2 Commits

Author SHA1 Message Date
sam_oneal 52a0d5c002 updated procedures in data.odin to pass url; added research notes for data-streaming
CI / Detect changed paths (pull_request) Successful in 9s
CI / Odin unit tests and build (pull_request) Successful in 1m23s
CI / API unit tests and lint (pull_request) Has been skipped
CI / Infra unit tests, vet, and preview (pull_request) Successful in 1m50s
2026-09-06 13:52:39 -06:00
sam_oneal ebaf956cdf added data file to store API objects and reaching out to API 2026-09-06 01:16:47 -06:00
43 changed files with 154 additions and 2381 deletions
-9
View File
@@ -66,15 +66,6 @@ jobs:
with: with:
submodules: recursive submodules: recursive
# Cache the extracted Odin install so only the first run downloads the
# ~60 MB tarball; keyed on the pinned version (cache paths live outside
# the checkout, so no sharing between jobs/refs).
- name: Cache Odin
uses: actions/cache@v4
with:
path: /tmp/odin
key: odin-${{ env.ODIN_VERSION }}-${{ runner.arch }}
- name: Install Odin ${{ env.ODIN_VERSION }} - name: Install Odin ${{ env.ODIN_VERSION }}
run: scripts/install_odin.sh run: scripts/install_odin.sh
-18
View File
@@ -34,12 +34,6 @@ jobs:
with: with:
submodules: recursive submodules: recursive
- name: Cache Odin
uses: actions/cache@v4
with:
path: /tmp/odin
key: odin-${{ env.ODIN_VERSION }}-${{ runner.arch }}
- name: Install Odin ${{ env.ODIN_VERSION }} - name: Install Odin ${{ env.ODIN_VERSION }}
run: scripts/install_odin.sh run: scripts/install_odin.sh
@@ -82,12 +76,6 @@ jobs:
with: with:
submodules: recursive submodules: recursive
- name: Cache Odin
uses: actions/cache@v4
with:
path: /tmp/odin
key: odin-${{ env.ODIN_VERSION }}-${{ runner.arch }}
- name: Install Odin ${{ env.ODIN_VERSION }} - name: Install Odin ${{ env.ODIN_VERSION }}
run: scripts/install_odin.sh run: scripts/install_odin.sh
@@ -151,12 +139,6 @@ jobs:
with: with:
submodules: recursive submodules: recursive
- name: Cache Odin
uses: actions/cache@v4
with:
path: /tmp/odin
key: odin-${{ env.ODIN_VERSION }}-${{ runner.arch }}
- name: Install Odin ${{ env.ODIN_VERSION }} - name: Install Odin ${{ env.ODIN_VERSION }}
run: scripts/install_odin.sh run: scripts/install_odin.sh
-3
View File
@@ -7,6 +7,3 @@ resources/ai/sessions
infra/Pulumi.*.yaml.backup infra/Pulumi.*.yaml.backup
infra/desi-explorer-infra infra/desi-explorer-infra
api/target/ api/target/
.env
.env.*
*.env
-1
View File
@@ -25,7 +25,6 @@ The root `Makefile` is a lean delegator: base commands (`run`, `build`, `test`,
- Odin code lives in `gui/src/`; external deps go in `gui/lib/` and are wired via `-collection:lib=lib/local` (or a git submodule imported by relative path). - Odin code lives in `gui/src/`; external deps go in `gui/lib/` and are wired via `-collection:lib=lib/local` (or a git submodule imported by relative path).
- Everything is plain `make` — no Taskfile — so CI (Gitea Actions) can call `make` directly. - Everything is plain `make` — no Taskfile — so CI (Gitea Actions) can call `make` directly.
- Keep the renderer (gui/), API (api/), and infra (infra/) logically separated; each owns its own Makefile, and the root Makefile is the only place that ties them together. - Keep the renderer (gui/), API (api/), and infra (infra/) logically separated; each owns its own Makefile, and the root Makefile is the only place that ties them together.
- **Diagrams in this repo's documentation are Mermaid flowcharts.** Gitea renders ` ```mermaid ` fenced blocks natively. Prefer a Mermaid flowchart over ASCII art / box-drawing diagrams; if a diagram genuinely can't be expressed as a flowchart, fall back to a plain text-based markdown diagram (e.g. a code block or table) rather than hand-rawn ASCII boxes.
## Gotchas ## Gotchas
- Odin version is pinned in `.gitea/workflows/*.yml` (`ODIN_VERSION`) and defaults in `scripts/install_odin.sh`; bump both together when tracking a new release. - Odin version is pinned in `.gitea/workflows/*.yml` (`ODIN_VERSION`) and defaults in `scripts/install_odin.sh`; bump both together when tracking a new release.
+7 -52
View File
@@ -9,36 +9,8 @@ GUI := gui
API := api API := api
INFRA := infra INFRA := infra
# Local dev/test assets live under resources/dev. These are the defaults for
# the run/*-web targets; override any of them on the command line, e.g.
# make run GUI_ENV_FILE=/path/to/gui.env API_DESI_DATA=/path/to/data.json
RESOURCE_DIR := $(CURDIR)/resources/dev
GUI_ENV_FILE ?= $(RESOURCE_DIR)/gui.env.example
API_ENV_FILE ?= $(RESOURCE_DIR)/api.env.example
API_DESI_DATA ?= $(RESOURCE_DIR)/desi_subset.json
# The API binds here for local dev (see api/src/config.rs); `make run` waits
# for $(API_HEALTH_URL) to respond before launching the GUI, so the renderer
# never races the API on startup. A bind host of 0.0.0.0 is probed via
# 127.0.0.1. Adjust API_WAIT_TIMEOUT (seconds) if the API takes longer than
# 60s to become healthy on a given machine.
#
# API_BIND_ADDR is only forwarded to the API when the user sets it explicitly
# (command line / environment). When it is left unset, the API reads it from
# $(API_ENV_FILE) (dotenvy applies env-file values that aren't already in the
# process environment), so the Makefile must not inject its own default here
# or it would shadow the env file. The effective bind address is read back
# from $(API_ENV_FILE) so the health check always polls the right port.
API_BIND_ADDR ?= 127.0.0.1:8080
API_BIND_EXPLICIT := $(filter command line environment,$(origin API_BIND_ADDR))
API_BIND_FROM_FILE := $(if $(API_BIND_EXPLICIT),,$(shell \
grep -E '^[[:space:]]*API_BIND_ADDR[[:space:]]*=' '$(API_ENV_FILE)' 2>/dev/null | \
tail -n 1 | sed 's/^[[:space:]]*API_BIND_ADDR[[:space:]]*=[[:space:]]*//; s/[[:space:]]*#.*$$//'))
API_BIND_EFFECTIVE := $(if $(API_BIND_EXPLICIT),$(API_BIND_ADDR),$(or $(API_BIND_FROM_FILE),$(API_BIND_ADDR)))
API_HEALTH_URL := http://$(subst 0.0.0.0,127.0.0.1,$(API_BIND_EFFECTIVE))/health
API_WAIT_TIMEOUT ?= 60
.PHONY: help setup run run-web build build-debug build-web test clean fmt \ .PHONY: help setup run run-web build build-debug build-web test clean fmt \
api-wait renovate-validate renovate-validate
help: ## List available targets help: ## List available targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
@@ -59,11 +31,10 @@ setup: ## Setup all sub-projects (submodules, gui deps, api deps, infra deps)
## ---- Renderer (Odin) ----------------------------------------------------- ## ---- Renderer (Odin) -----------------------------------------------------
run: ## Run the native app (gui/); waits for the API to be healthy first run: ## Run the native app (gui/)
@API_ENV_FILE="$(API_ENV_FILE)" API_DESI_DATA="$(API_DESI_DATA)" $(if $(API_BIND_EXPLICIT),API_BIND_ADDR="$(API_BIND_ADDR)",) $(MAKE) -C $(API) run & api_pid=$$!; \ @$(MAKE) -C $(API) run & api_pid=$$!; \
trap 'kill $$api_pid 2>/dev/null' INT TERM EXIT; \ trap 'kill $$api_pid 2>/dev/null' INT TERM EXIT; \
$(MAKE) api-wait || { kill $$api_pid 2>/dev/null; exit 1; }; \ $(MAKE) -C $(GUI) run; \
GUI_ENV_FILE="$(GUI_ENV_FILE)" $(MAKE) -C $(GUI) run; \
kill $$api_pid 2>/dev/null kill $$api_pid 2>/dev/null
build: ## Release build (gui/ + api/) build: ## Release build (gui/ + api/)
@@ -77,28 +48,12 @@ build-debug: ## Debug build (gui/ + api/)
build-web: ## WebAssembly build -> build/web (gui/, needs emscripten) build-web: ## WebAssembly build -> build/web (gui/, needs emscripten)
@$(MAKE) -C $(GUI) build-web @$(MAKE) -C $(GUI) build-web
run-web: ## Start WASM build + API server for web dev (waits for API health) run-web: ## Start WASM build + API server for web dev
@API_ENV_FILE="$(API_ENV_FILE)" API_DESI_DATA="$(API_DESI_DATA)" $(if $(API_BIND_EXPLICIT),API_BIND_ADDR="$(API_BIND_ADDR)",) $(MAKE) -C $(API) run & api_pid=$$!; \ @$(MAKE) -C $(API) run & api_pid=$$!; \
trap 'kill $$api_pid 2>/dev/null' INT TERM EXIT; \ trap 'kill $$api_pid 2>/dev/null' INT TERM EXIT; \
$(MAKE) api-wait || { kill $$api_pid 2>/dev/null; exit 1; }; \ $(MAKE) -C $(GUI) build-web; \
GUI_ENV_FILE="$(GUI_ENV_FILE)" $(MAKE) -C $(GUI) build-web; \
kill $$api_pid 2>/dev/null kill $$api_pid 2>/dev/null
## ---- Dev helpers ----------------------------------------------------------
api-wait: ## Poll the API health endpoint until it responds (or times out)
@echo "Waiting for API at $(API_HEALTH_URL) ..."; \
elapsed=0; \
while ! curl -sf "$(API_HEALTH_URL)" >/dev/null 2>&1; do \
elapsed=$$((elapsed + 1)); \
if [ "$${elapsed}" -ge "$(API_WAIT_TIMEOUT)" ]; then \
echo "Error: API at $(API_HEALTH_URL) not healthy after $(API_WAIT_TIMEOUT)s" >&2; \
exit 1; \
fi; \
sleep 1; \
done; \
echo "API is up."
## ---- Aggregates ---------------------------------------------------------- ## ---- Aggregates ----------------------------------------------------------
test: ## Test all projects (odin + cargo + go) test: ## Test all projects (odin + cargo + go)
-25
View File
@@ -25,7 +25,6 @@ A fun personal project with three interlocking goals:
Early scaffolding. The application currently: Early scaffolding. The application currently:
- Opens a resizable 3D raylib window with an orbital camera (zoom + rotate + pan). - Opens a resizable 3D raylib window with an orbital camera (zoom + rotate + pan).
- Starts on a launch screen where you configure the API URL, pick a region, hit **Refresh catalogs** to pull the catalog list, and press **Explore** to enter the 3D view (TAB returns to the launch screen).
- Renders a procedurally generated point cloud standing in for the galaxy catalog (real DESI data ingestion is the next milestone). - Renders a procedurally generated point cloud standing in for the galaxy catalog (real DESI data ingestion is the next milestone).
- Compiles natively, to WebAssembly, and is deployed to an on-prem Kubernetes cluster as a placeholder web service. - Compiles natively, to WebAssembly, and is deployed to an on-prem Kubernetes cluster as a placeholder web service.
@@ -80,30 +79,6 @@ make clean # remove build artifacts from all projects
make fmt # format all projects make fmt # format all projects
``` ```
`make run` / `make run-web` configure the API and renderer from the example
assets under `resources/dev/` (see the dotenv lib in `gui/lib/local/dotenv`).
Override any of them on the command line:
```sh
make run \
GUI_ENV_FILE=/path/to/gui.env \
API_ENV_FILE=/path/to/api.env \
API_DESI_DATA=/path/to/desi_data.json
```
The defaults point at `resources/dev/gui.env.example` (renderer's `API_URL`),
`resources/dev/api.env.example` (API `API_BIND_ADDR`), and
`resources/dev/desi_subset.json` (a small JSON catalog subset served by the
API's `/api/v1/catalogs` and `/api/v1/objects` endpoints).
`make run` and `make run-web` start the API in the background and then wait for
its `/health` endpoint before launching the GUI (`make api-wait`), so the
renderer never races the API on startup. Override the bind address and worst
case wait with `API_BIND_ADDR` (default `127.0.0.1:8080`) and
`API_WAIT_TIMEOUT` (default 60s). The earlier you click **Refresh** on the GUI's
launch screen, the more likely you are to catch the API mid-boot; a slow API can
also be re-polled by pressing **Refresh** again.
Project-specific targets live in their own `Makefile` and are reached with Project-specific targets live in their own `Makefile` and are reached with
`make -C <dir> <target>`: `make -C <dir> <target>`:
+2 -10
View File
@@ -99,7 +99,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
"dotenvy",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
@@ -109,12 +108,6 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
] ]
[[package]]
name = "dotenvy"
version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.14" version = "0.3.14"
@@ -565,15 +558,14 @@ dependencies = [
[[package]] [[package]]
name = "tower-http" name = "tower-http"
version = "0.7.1" version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"bytes", "bytes",
"http", "http",
"http-body", "http-body",
"percent-encoding",
"pin-project-lite", "pin-project-lite",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
+4 -5
View File
@@ -5,18 +5,17 @@ edition = "2021"
description = "Backend API for DESI Explorer — serves DESI survey catalog data" description = "Backend API for DESI Explorer — serves DESI survey catalog data"
[dependencies] [dependencies]
anyhow = "1"
axum = "0.8" axum = "0.8"
dotenvy = "0.15"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] } tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
tower-http = { version = "0.7", features = ["cors", "trace"] } serde = { version = "1", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tower-http = { version = "0.6", features = ["cors", "trace"] }
anyhow = "1"
[dev-dependencies] [dev-dependencies]
tower = { version = "0.5", features = ["util"] } tower = { version = "0.5", features = ["util"] }
serde_json = "1"
[profile.release] [profile.release]
lto = true lto = true
-14
View File
@@ -4,20 +4,6 @@
# `api-*` convenience targets). # `api-*` convenience targets).
CARGO ?= cargo CARGO ?= cargo
ROOT := ..
# Normalize API_ENV_FILE / API_DESI_DATA (given relative to the repo root) to
# absolute paths so the API process can open them regardless of its working
# directory. "override" is required because they are usually passed as
# command-line/env vars, which would otherwise override any assignment here.
define normalize_path
ifdef $1
ifneq ($(abspath $($1)),$($1))
override $1 := $(abspath $(ROOT)/$($1))
endif
endif
endef
$(foreach v,API_ENV_FILE API_DESI_DATA,$(eval $(call normalize_path,$v)))
.PHONY: help setup run build test check fmt clean .PHONY: help setup run build test check fmt clean
+1 -14
View File
@@ -1,10 +1,5 @@
use std::path::PathBuf;
pub struct Config { pub struct Config {
pub bind_addr: String, pub bind_addr: String,
/// Path to a DESI data file (JSON) to serve; `None` falls back to the
/// built-in placeholder catalogs.
pub desi_data: Option<PathBuf>,
} }
impl Config { impl Config {
@@ -12,14 +7,6 @@ impl Config {
let bind_addr = let bind_addr =
std::env::var("API_BIND_ADDR").unwrap_or_else(|_| "0.0.0.0:8080".to_string()); std::env::var("API_BIND_ADDR").unwrap_or_else(|_| "0.0.0.0:8080".to_string());
let desi_data = std::env::var("API_DESI_DATA") Ok(Self { bind_addr })
.ok()
.filter(|s| !s.is_empty())
.map(PathBuf::from);
Ok(Self {
bind_addr,
desi_data,
})
} }
} }
-1
View File
@@ -1,4 +1,3 @@
pub mod config; pub mod config;
pub mod models; pub mod models;
pub mod routes; pub mod routes;
pub mod store;
+2 -40
View File
@@ -1,7 +1,4 @@
use std::path::Path; use desi_explorer_api::{config, routes};
use std::sync::Arc;
use desi_explorer_api::{config, routes, store};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
@@ -14,22 +11,8 @@ async fn main() -> anyhow::Result<()> {
) )
.init(); .init();
load_env_file()?;
let config = config::Config::from_env()?; let config = config::Config::from_env()?;
let app = routes::app();
let catalog_store = match &config.desi_data {
Some(path) => {
tracing::info!(path = %path.display(), "loading DESI data");
store::CatalogStore::load(Path::new(path))?
}
None => {
tracing::warn!("API_DESI_DATA not set, serving placeholder catalogs");
store::CatalogStore::placeholder()
}
};
let app = routes::app_with_state(Arc::new(catalog_store));
let listener = tokio::net::TcpListener::bind(&config.bind_addr).await?; let listener = tokio::net::TcpListener::bind(&config.bind_addr).await?;
tracing::info!("DESI Explorer API listening on {}", config.bind_addr); tracing::info!("DESI Explorer API listening on {}", config.bind_addr);
@@ -41,27 +24,6 @@ async fn main() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
/// Loads the env file named by `API_ENV_FILE` (if set) into the process
/// environment. Existing env vars are not overridden, so values passed
/// directly on the command line or by the Makefile take precedence.
fn load_env_file() -> anyhow::Result<()> {
let path = std::env::var("API_ENV_FILE").unwrap_or_default();
if path.is_empty() {
return Ok(());
}
match dotenvy::from_path(&path) {
Ok(_) => tracing::info!(%path, "loaded env file"),
Err(err) => {
return Err(anyhow::anyhow!(
"failed to load API_ENV_FILE {path:?}: {err}"
))
}
}
Ok(())
}
async fn shutdown_signal() { async fn shutdown_signal() {
let _ = tokio::signal::ctrl_c().await; let _ = tokio::signal::ctrl_c().await;
tracing::info!("shutting down"); tracing::info!("shutting down");
+4 -4
View File
@@ -1,17 +1,17 @@
use serde::{Deserialize, Serialize}; use serde::Serialize;
/// Catalog metadata for a DESI data release/survey. /// Catalog metadata for a DESI data release/survey.
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct Catalog { pub struct Catalog {
pub name: String, pub name: String,
pub release: String, pub release: String,
pub description: String, pub description: &'static str,
pub object_count: Option<u64>, pub object_count: Option<u64>,
} }
/// A single catalog object (galaxy / quasar / star) with its survey /// A single catalog object (galaxy / quasar / star) with its survey
/// coordinates. `ra` and `dec` are in degrees; `redshift` is dimensionless. /// coordinates. `ra` and `dec` are in degrees; `redshift` is dimensionless.
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Serialize)]
pub struct CatalogObject { pub struct CatalogObject {
pub id: String, pub id: String,
pub catalog: String, pub catalog: String,
+30 -24
View File
@@ -1,16 +1,34 @@
use std::sync::Arc;
use axum::{ use axum::{
extract::{Query, State}, extract::Query,
http::StatusCode,
response::{IntoResponse, Response},
Json, Json,
}; };
use serde::Deserialize; use serde::Deserialize;
use std::sync::LazyLock;
use crate::models::{Catalog, CatalogObject}; use crate::models::{Catalog, CatalogObject};
use crate::store::CatalogStore;
pub async fn list_catalogs(State(state): State<Arc<CatalogStore>>) -> Json<Vec<Catalog>> { /// Placeholder catalogs until real DESI EDR/DR1 ingestion lands.
Json(state.catalogs.clone()) static CATALOGS: LazyLock<Vec<Catalog>> = LazyLock::new(|| {
vec![
Catalog {
name: "edr".to_string(),
release: "EDR".to_string(),
description: "DESI Early Data Release",
object_count: None,
},
Catalog {
name: "dr1".to_string(),
release: "DR1".to_string(),
description: "DESI Data Release 1",
object_count: None,
},
]
});
pub async fn list_catalogs() -> Json<Vec<Catalog>> {
Json(CATALOGS.clone())
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -20,29 +38,17 @@ pub struct ObjectQuery {
limit: Option<usize>, limit: Option<usize>,
} }
pub async fn list_objects( /// Placeholder object query. Real implementation will page through the
State(state): State<Arc<CatalogStore>>, /// centralized DESI catalog store rather than return an empty result set.
Query(query): Query<ObjectQuery>, pub async fn list_objects(Query(query): Query<ObjectQuery>) -> Response {
) -> Json<Vec<CatalogObject>> {
let limit = query.limit.unwrap_or(100).min(10_000); let limit = query.limit.unwrap_or(100).min(10_000);
tracing::debug!( tracing::debug!(
%limit, %limit,
catalog = query.catalog.as_deref().unwrap_or("all"), catalog = query.catalog.as_deref().unwrap_or("all"),
objects = state.objects.len(), "querying catalog objects (placeholder)"
"querying catalog objects"
); );
let objects: Vec<CatalogObject> = match &query.catalog { let objects: Vec<CatalogObject> = Vec::new();
Some(catalog) => state (StatusCode::OK, Json(objects)).into_response()
.objects
.iter()
.filter(|o| &o.catalog == catalog)
.take(limit)
.cloned()
.collect(),
None => state.objects.iter().take(limit).cloned().collect(),
};
Json(objects)
} }
+2 -12
View File
@@ -1,23 +1,13 @@
pub mod catalogs; pub mod catalogs;
pub mod health; pub mod health;
use std::sync::Arc;
use axum::{routing::get, Router}; use axum::{routing::get, Router};
use crate::store::CatalogStore; /// Builds the application router. Kept separate from `main` so tests can
/// construct it without binding a socket.
/// Builds the application router with a static placeholder store. Kept
/// separate from `main` so tests can construct it without binding a socket.
pub fn app() -> Router { pub fn app() -> Router {
app_with_state(Arc::new(CatalogStore::placeholder()))
}
/// Builds the application router serving the given catalog store.
pub fn app_with_state(state: Arc<CatalogStore>) -> Router {
Router::new() Router::new()
.route("/health", get(health::health)) .route("/health", get(health::health))
.route("/api/v1/catalogs", get(catalogs::list_catalogs)) .route("/api/v1/catalogs", get(catalogs::list_catalogs))
.route("/api/v1/objects", get(catalogs::list_objects)) .route("/api/v1/objects", get(catalogs::list_objects))
.with_state(state)
} }
-57
View File
@@ -1,57 +0,0 @@
use std::path::Path;
use serde::Deserialize;
use crate::models::{Catalog, CatalogObject};
/// In-memory catalog store, shared (via `Arc`) across routes. Populated either
/// from a DESI data file loaded at startup or from `placeholder`.
#[derive(Debug, Clone, Default)]
pub struct CatalogStore {
pub catalogs: Vec<Catalog>,
pub objects: Vec<CatalogObject>,
}
/// JSON layout of the DESI data file referenced by `API_DESI_DATA`.
#[derive(Debug, Deserialize)]
pub struct DataFile {
pub catalogs: Vec<Catalog>,
#[serde(default)]
pub objects: Vec<CatalogObject>,
}
impl CatalogStore {
/// Static fallback catalogs used when no `API_DESI_DATA` file is given
/// (and by the `routes::app()` test helper).
pub fn placeholder() -> Self {
Self {
catalogs: vec![
Catalog {
name: "edr".to_string(),
release: "EDR".to_string(),
description: "DESI Early Data Release".to_string(),
object_count: None,
},
Catalog {
name: "dr1".to_string(),
release: "DR1".to_string(),
description: "DESI Data Release 1".to_string(),
object_count: None,
},
],
objects: Vec::new(),
}
}
/// Loads catalogs and objects from a JSON data file. Errors on unreadable
/// files or malformed JSON so the caller can fail loudly instead of
/// silently serving empty data.
pub fn load(path: &Path) -> anyhow::Result<Self> {
let text = std::fs::read_to_string(path)?;
let file: DataFile = serde_json::from_str(&text)?;
Ok(Self {
catalogs: file.catalogs,
objects: file.objects,
})
}
}
-93
View File
@@ -1,93 +0,0 @@
use axum::body::{to_bytes, Body};
use axum::http::{Request, StatusCode};
use std::sync::Arc;
use tower::ServiceExt;
use desi_explorer_api::models::CatalogObject;
use desi_explorer_api::routes;
use desi_explorer_api::store::CatalogStore;
fn sample_store() -> CatalogStore {
CatalogStore {
catalogs: Vec::new(),
objects: vec![
CatalogObject {
id: "o1".to_string(),
catalog: "edr".to_string(),
object_type: "GALAXY".to_string(),
ra: 1.5,
dec: 2.5,
redshift: 0.8,
},
CatalogObject {
id: "o2".to_string(),
catalog: "dr1".to_string(),
object_type: "STAR".to_string(),
ra: 3.5,
dec: 4.5,
redshift: 0.0,
},
],
}
}
#[tokio::test]
async fn objects_returns_all_when_no_filter() {
let app = routes::app_with_state(Arc::new(sample_store()));
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/objects")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
let objects: Vec<CatalogObject> = serde_json::from_slice(&body).unwrap();
assert_eq!(objects.len(), 2);
}
#[tokio::test]
async fn objects_filters_by_catalog() {
let app = routes::app_with_state(Arc::new(sample_store()));
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/objects?catalog=edr")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
let objects: Vec<CatalogObject> = serde_json::from_slice(&body).unwrap();
assert_eq!(objects.len(), 1);
assert_eq!(objects[0].id, "o1");
}
#[tokio::test]
async fn objects_respects_limit() {
let app = routes::app_with_state(Arc::new(sample_store()));
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/objects?limit=1")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = to_bytes(response.into_body(), usize::MAX).await.unwrap();
let objects: Vec<CatalogObject> = serde_json::from_slice(&body).unwrap();
assert_eq!(objects.len(), 1);
}
-54
View File
@@ -1,54 +0,0 @@
use desi_explorer_api::store::CatalogStore;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn load_parses_catalogs_and_objects() {
let json = r#"{
"catalogs": [
{"name":"edr","release":"EDR","description":"test","object_count":2}
],
"objects": [
{"id":"o1","catalog":"edr","object_type":"GALAXY","ra":1.5,"dec":2.5,"redshift":0.8}
]
}"#;
let dir = std::env::temp_dir().join(format!(
"desi_explorer_store_{}_{}",
std::process::id(),
line!()
));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("data.json");
std::fs::write(&path, json).unwrap();
let store = CatalogStore::load(&path).unwrap();
let _ = std::fs::remove_dir_all(&dir);
assert_eq!(store.catalogs.len(), 1);
assert_eq!(store.catalogs[0].name, "edr");
assert_eq!(store.catalogs[0].object_count, Some(2));
assert_eq!(store.objects.len(), 1);
assert_eq!(store.objects[0].id, "o1");
assert_eq!(store.objects[0].ra, 1.5);
}
#[test]
fn load_rejects_malformed_json() {
let dir = std::env::temp_dir().join(format!(
"desi_explorer_store_{}_{}",
std::process::id(),
line!()
));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("data.json");
std::fs::write(&path, "not json").unwrap();
let result = CatalogStore::load(&path);
let _ = std::fs::remove_dir_all(&dir);
assert!(result.is_err());
}
}
+2 -27
View File
@@ -8,28 +8,13 @@
# output dirs live at the repo root and are referenced through `$(ROOT)`. # output dirs live at the repo root and are referenced through `$(ROOT)`.
ODIN ?= odin ODIN ?= odin
GDB ?= gdb
ROOT := .. ROOT := ..
BIN := $(ROOT)/bin BIN := $(ROOT)/bin
BINARY := $(BIN)/desi_explorer BINARY := $(BIN)/desi_explorer
ODIN_FLAGS := -collection:lib=lib/local ODIN_FLAGS := -collection:lib=lib/local
WASM_DEFINE := RAYLIB_WASM_LIB=env.o WASM_DEFINE := RAYLIB_WASM_LIB=env.o
# Normalize GUI_ENV_FILE (given relative to the repo root) to an absolute path .PHONY: help setup add-dep run build build-debug build-web test clean fmt
# so the Odin process can open it regardless of its working directory.
# "override" is required because GUI_ENV_FILE is usually set on the command
# line (or passed as an env var to this sub-make), which would otherwise
# override any assignment made here.
ifdef GUI_ENV_FILE
ifneq ($(abspath $(GUI_ENV_FILE)),$(GUI_ENV_FILE))
override GUI_ENV_FILE := $(abspath $(ROOT)/$(GUI_ENV_FILE))
endif
endif
# "override" on a command-line variable silently drops it from the recipe
# environment; re-export it so the app can find its env file (run + gdb).
export GUI_ENV_FILE
.PHONY: help setup add-dep run build build-debug gdb build-web test clean fmt
help: ## List available targets help: ## List available targets
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
@@ -56,22 +41,12 @@ build-debug: ## Debug build -> bin/desi_explorer
@mkdir -p lib/local $(BIN) @mkdir -p lib/local $(BIN)
$(ODIN) build src $(ODIN_FLAGS) -o:none -debug -out:$(BINARY) $(ODIN) build src $(ODIN_FLAGS) -o:none -debug -out:$(BINARY)
gdb: build-debug ## Run the native app under gdb (type 'run', then 'bt' on a crash)
$(GDB) -q --args $(BINARY) $(ARGS)
build-web: ## WebAssembly build -> build/web (needs emscripten) build-web: ## WebAssembly build -> build/web (needs emscripten)
@scripts/build_web.sh @scripts/build_web.sh
test: ## Run Odin unit tests test: ## Run Odin unit tests
@mkdir -p lib/local @mkdir -p lib/local
@if ls test/*.odin >/dev/null 2>&1; then \ $(ODIN) test src $(ODIN_FLAGS)
echo "== gui/test =="; \
$(ODIN) test test $(ODIN_FLAGS); \
fi
@for dir in $$(find lib/local -name '*_test.odin' -exec dirname {} \; | sort -u); do \
echo "== $$dir =="; \
$(ODIN) test "$$dir" $(ODIN_FLAGS); \
done
clean: ## Remove build artifacts clean: ## Remove build artifacts
rm -rf $(BIN) build rm -rf $(BIN) build
-167
View File
@@ -1,167 +0,0 @@
package dotenv
import "base:runtime"
import "core:os"
import "core:reflect"
import "core:strconv"
import "core:strings"
// parse parses dotenv-format source (KEY=VALUE lines) into a map allocated
// with allocator. Blank lines, lines starting with '#', and lines without a
// '=' are skipped. Keys and values are trimmed; values may be wrapped in
// double quotes. Real process environment variables take precedence over the
// file. The returned map owns its keys/values; release it with destroy.
@(private)
parse :: proc(src: string, allocator := context.allocator) -> map[string]string {
result := make(map[string]string, allocator)
it := src
for line in strings.split_lines_iterator(&it) {
tr := strings.trim_space(line)
if len(tr) == 0 || strings.has_prefix(tr, "#") {
continue
}
eq := strings.index_byte(tr, '=')
if eq < 0 {
continue
}
key := strings.trim_space(tr[:eq])
if key == "" {
continue
}
value := strings.trim_space(tr[eq + 1:])
if len(value) >= 2 && value[0] == '"' && value[len(value) - 1] == '"' {
value = value[1:len(value) - 1]
}
// real process env vars win over the file
if override, found := os.lookup_env(key, allocator); found {
result[strings.clone(key, allocator)] = override
continue
}
// clone so the map outlives the source buffer (e.g. a freed file read)
result[strings.clone(key, allocator)] = strings.clone(value, allocator)
}
return result
}
// parse_file reads a dotenv file from disk and parses it into a map. It
// returns (nil, false) when the file cannot be read (e.g. it does not exist).
parse_file :: proc(filename: string, allocator := context.allocator) -> (map[string]string, bool) {
data, err := os.read_entire_file(filename, allocator)
if err != nil {
return nil, false
}
defer delete(data, allocator)
return parse(string(data), allocator), true
}
// destroy frees the cloned keys/values and the map itself. Use it to release
// a map returned by parse/parse_file (plain delete does not free the strings).
// Any allocator passed to parse/parse_file must be passed here too.
destroy :: proc(env: map[string]string, allocator := context.allocator) {
for key, value in env {
delete(key, allocator)
delete(value, allocator)
}
delete(env)
}
// env_key returns the env key that should bind to a struct field. It prefers
// an explicit `env:"NAME"` tag; when the tag is absent or empty it falls
// back to the field's name. Matching against the parsed map is
// case-insensitive, so API_URL maps onto api_url (or an `env:"API_URL"` tag).
@(private)
env_key_for_field :: proc(field: reflect.Struct_Field) -> string {
if tag_key, ok := reflect.struct_tag_lookup(field.tag, "env"); ok && tag_key != "" {
return tag_key
}
return field.name
}
// decode populates dest's fields from env, matching each field by name
// (or by an `env:"NAME"` struct tag). Values are converted to the field's
// type: string is cloned as-is into allocator, integers are parsed with
// strconv.parse_int (decimal/hex/negative), booleans with strconv.parse_bool,
// and floats with strconv.parse_f64. Keys missing from env leave the field
// at its zero value. It returns false if a present value cannot be converted
// to the field's type.
decode :: proc(env: map[string]string, dest: ^$T, allocator := context.allocator) -> bool {
ti := reflect.type_info_base(type_info_of(T))
fields, ok := ti.variant.(runtime.Type_Info_Struct)
if !ok {
return false
}
value: string
field_ptr := rawptr(dest)
st: reflect.Struct_Field
for _, i in fields.names[:fields.field_count] {
st = reflect.struct_field_at(T, i)
name := env_key_for_field(st)
value = ""
found := false
for key, v in env {
if key == name || strings.equal_fold(key, name) {
value, found = v, true
break
}
}
if !found {
continue
}
field_ptr = rawptr(uintptr(dest) + fields.offsets[i])
field_ti := reflect.type_info_base(fields.types[i])
#partial switch variant in field_ti.variant {
case runtime.Type_Info_String:
(^string)(field_ptr)^ = strings.clone(value, allocator)
case runtime.Type_Info_Integer:
parsed, err := strconv.parse_int(value)
if !err {
return false
}
switch field_ti.size {
case 1:
(^i8)(field_ptr)^ = cast(i8)parsed
case 2:
(^i16)(field_ptr)^ = cast(i16)parsed
case 4:
(^i32)(field_ptr)^ = cast(i32)parsed
case 8:
(^i64)(field_ptr)^ = cast(i64)parsed
case:
return false
}
case runtime.Type_Info_Boolean:
parsed, err := strconv.parse_bool(value)
if !err {
return false
}
(^bool)(field_ptr)^ = parsed
case runtime.Type_Info_Float:
parsed, err := strconv.parse_f64(value)
if !err {
return false
}
switch field_ti.size {
case 4:
(^f32)(field_ptr)^ = cast(f32)parsed
case 8:
(^f64)(field_ptr)^ = parsed
case:
return false
}
case:
// unsupported field type (slices, pointers, ...) is left untouched
}
}
return true
}
-245
View File
@@ -1,245 +0,0 @@
package dotenv_tests
import "core:os"
import "core:strings"
import "core:testing"
import dotenv "lib:dotenv/src"
Test_Config :: struct {
api_url: string,
debug: bool,
port: int,
ratio: f64,
}
Tagged_Config :: struct {
api_url: string `env:"API_URL"`,
port: int `env:"PORT"`,
debug: bool `env:"DEBUG"`,
}
Tagged_Empty :: struct {
api_url: string `env:""`,
}
// load_env writes src to a unique temp file and parses it via parse_file.
// The returned map owns its strings; callers must destroy it.
load_env :: proc(t: ^testing.T, src: string) -> map[string]string {
dir, err := os.make_directory_temp("", "dotenv_test_*", context.allocator)
testing.expect(t, err == nil, "expected temp dir to be created")
defer os.remove_all(dir)
defer delete(dir)
path := strings.concatenate({dir, "/.env"})
defer delete(path)
testing.expect(
t,
os.write_entire_file(path, src) == nil,
"expected file write to succeed",
)
env, ok := dotenv.parse_file(path)
testing.expect(t, ok, "expected parse_file to succeed")
return env
}
@(test)
test_parse_basic :: proc(t: ^testing.T) {
env := load_env(t, "API_URL=http://127.0.0.1:8080\nDEBUG=true\nPORT=8080\n")
defer dotenv.destroy(env)
testing.expect(t, env["API_URL"] == "http://127.0.0.1:8080")
testing.expect(t, env["DEBUG"] == "true")
testing.expect(t, env["PORT"] == "8080")
}
@(test)
test_parse_ignores_comments_and_blank_lines :: proc(t: ^testing.T) {
env := load_env(t, "# leading comment\n\n \nFOO=bar \nBAZ = qux \n")
defer dotenv.destroy(env)
testing.expect(t, env["FOO"] == "bar", "value should be trimmed")
testing.expect(
t,
env["BAZ"] == "qux",
"key and value should be trimmed around '='",
)
testing.expect(
t,
"API_URL" not_in env,
"comment-only lines should not be parsed",
)
}
@(test)
test_parse_quoted_values :: proc(t: ^testing.T) {
env := load_env(t, "GREETING=\"hello world\"\nEMPTY=\"\"\n")
defer dotenv.destroy(env)
testing.expect(
t,
env["GREETING"] == "hello world",
"quoted value with inner space",
)
testing.expect(t, env["EMPTY"] == "", "double-quoted empty value")
}
@(test)
test_parse_skips_lines_without_equals :: proc(t: ^testing.T) {
env := load_env(t, "not-an-assignment\nOK=yep\n")
defer dotenv.destroy(env)
testing.expect(t, env["OK"] == "yep")
testing.expect(
t,
"not-an-assignment" not_in env,
"line without '=' should be skipped",
)
}
@(test)
test_parse_missing_file :: proc(t: ^testing.T) {
env, ok := dotenv.parse_file("/nonexistent/dotenv_test_does_not_exist.env")
testing.expect(t, !ok, "missing file should report failure")
testing.expect(t, env == nil, "missing file should return nil map")
}
@(test)
test_real_env_overrides_file :: proc(t: ^testing.T) {
testing.expect(t, os.set_env("DESI_EXPLORER_TEST_FOO", "from_env") == nil)
defer os.unset_env("DESI_EXPLORER_TEST_FOO")
env := load_env(t, "DESI_EXPLORER_TEST_FOO=from_file\n")
defer dotenv.destroy(env)
testing.expect(
t,
env["DESI_EXPLORER_TEST_FOO"] == "from_env",
"real env var should win over file",
)
}
@(test)
test_decode_maps_fields_case_insensitively :: proc(t: ^testing.T) {
env := load_env(
t,
"API_URL=http://127.0.0.1:8080\nDEBUG=true\nPORT=8080\nRATIO=0.5\n",
)
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
defer delete(cfg.api_url)
testing.expect(
t,
cfg.api_url == "http://127.0.0.1:8080",
"API_URL maps onto api_url",
)
testing.expect(t, cfg.debug == true, "DEBUG=true should decode to true")
testing.expect(t, cfg.port == 8080, "PORT=8080 should decode to int 8080")
testing.expect(t, cfg.ratio == 0.5, "RATIO=0.5 should decode to f64 0.5")
}
@(test)
test_decode_matches_exact_and_lowercase_keys :: proc(t: ^testing.T) {
env := load_env(t, "api_url=http://exact\nPort=9090\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
defer delete(cfg.api_url)
testing.expect(
t,
cfg.api_url == "http://exact",
"exact-case key should match",
)
testing.expect(t, cfg.port == 9090, "mixed-case key should match field")
}
@(test)
test_decode_missing_keys_leave_zero_values :: proc(t: ^testing.T) {
env := load_env(t, "UNRELATED=value\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
testing.expect(t, cfg.api_url == "")
testing.expect(t, !cfg.debug)
testing.expect(t, cfg.port == 0)
testing.expect(t, cfg.ratio == 0)
}
@(test)
test_decode_unparsable_int_fails :: proc(t: ^testing.T) {
env := load_env(t, "PORT=oops\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(
t,
!dotenv.decode(env, &cfg),
"unparsable int should make decode fail",
)
}
@(test)
test_decode_unparsable_bool_fails :: proc(t: ^testing.T) {
env := load_env(t, "DEBUG=maybe\nAPI_URL=http://127.0.0.1:8080\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(
t,
!dotenv.decode(env, &cfg),
"unparsable bool should make decode fail",
)
defer delete(cfg.api_url)
}
@(test)
test_decode_hex_and_negative_ints :: proc(t: ^testing.T) {
env := load_env(t, "PORT=0x1F\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
testing.expect(t, cfg.port == 31, "hex int should decode")
}
@(test)
test_decode_honors_env_tags :: proc(t: ^testing.T) {
env := load_env(t, "API_URL=http://127.0.0.1:8080\nPORT=9090\nDEBUG=true\n")
defer dotenv.destroy(env)
cfg := Tagged_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
defer delete(cfg.api_url)
testing.expect(
t,
cfg.api_url == "http://127.0.0.1:8080",
"env tag should bind API_URL",
)
testing.expect(t, cfg.port == 9090, "env tag should bind PORT")
testing.expect(t, cfg.debug == true, "env tag should bind DEBUG")
}
@(test)
test_decode_empty_env_tag_falls_back_to_field_name :: proc(t: ^testing.T) {
env := load_env(t, "api_url=http://fallback\n")
defer dotenv.destroy(env)
cfg := Tagged_Empty{}
testing.expect(t, dotenv.decode(env, &cfg))
defer delete(cfg.api_url)
testing.expect(
t,
cfg.api_url == "http://fallback",
"empty env tag should use field name",
)
}
-327
View File
@@ -1,327 +0,0 @@
package http
import "core:fmt"
import "core:log"
import "core:net"
import "core:strconv"
import "core:strings"
import "core:time"
DEFAULT_HTTP_PORT :: 80
DEFAULT_HTTPS_PORT :: 443
MAX_REDIRECTS :: 5
HTTP_TIMEOUT :: 5 * time.Second
Response :: struct {
status: int,
location: string,
body: string,
content_length: int,
chunked: bool,
}
Error :: struct {
code: int,
message: string,
}
Error_Code :: enum int {
Invalid_Url,
Dial_Failed,
Send_Failed,
Invalid_Response,
Invalid_Redirect,
Invalid_Chunked,
Too_Many_Redirects,
}
error_make :: proc(code: Error_Code, msg: string = "") -> ^Error {
err := new(Error)
err.code = int(code)
err.message = msg
if msg == "" {
switch code {
case .Invalid_Url:
err.message = "invalid url"
case .Dial_Failed:
err.message = "could not connect to host"
case .Send_Failed:
err.message = "could not send request"
case .Invalid_Response:
err.message = "could not parse response"
case .Invalid_Redirect:
err.message = "invalid redirect"
case .Invalid_Chunked:
err.message = "invalid chunked response"
case .Too_Many_Redirects:
err.message = "too many redirects"
}
}
return err
}
http_get :: proc(url: string) -> (resp: Response, err: ^Error) {
current := url
owns := false
for _ in 0 ..= MAX_REDIRECTS {
next_url, next_resp, request_err := perform_request(current)
if owns do delete(current)
if request_err != nil {
return {}, request_err
}
if next_url != "" {
current = next_url
owns = true
continue
}
return next_resp, nil
}
if owns do delete(current)
return {}, error_make(.Too_Many_Redirects)
}
perform_request :: proc(url: string) -> (next_url: string, resp: Response, err: ^Error) {
_, host, port, path, parse_ok := parse_http_url(url)
if !parse_ok {
return "", {}, error_make(.Invalid_Url)
}
conn, dial_err := net.dial_tcp_from_host_or_endpoint(net.Host{host, port})
if dial_err != nil {
return "", {}, error_make(.Dial_Failed)
}
defer net.close(conn)
net.set_option(conn, .Receive_Timeout, HTTP_TIMEOUT)
net.set_option(conn, .Send_Timeout, HTTP_TIMEOUT)
host_buf: [256]byte
host_head := host
if port != DEFAULT_HTTP_PORT {
host_head = fmt.bprintf(host_buf[:], "%s:%d", host, port)
}
request_buf: [1024]byte
request := fmt.bprintf(
request_buf[:],
"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\nUser-Agent: desi-explorer-http\r\nAccept: */*\r\n\r\n",
path,
host_head,
)
if _, send_err := net.send_tcp(conn, transmute([]u8)request); send_err != nil {
return "", {}, error_make(.Send_Failed)
}
buf: [dynamic]u8
defer delete(buf)
scratch: [4096]byte
for {
n, recv_err := net.recv_tcp(conn, scratch[:])
if n > 0 do append(&buf, ..scratch[:n])
if recv_err != nil || n == 0 do break
}
parsed, response_ok := parse_response(buf[:])
if !response_ok {
return "", {}, error_make(.Invalid_Response)
}
resp = parsed
if resp.status >= 300 && resp.status < 400 && resp.location != "" {
resolved := resolve_redirect(url, resp.location)
if resolved == "" {
return "", {}, error_make(.Invalid_Redirect)
}
resp.body = ""
return resolved, resp, nil
}
if resp.chunked {
decoded, decode_ok := decode_chunked(resp.body)
if !decode_ok {
return "", {}, error_make(.Invalid_Chunked)
}
resp.body = decoded
return "", resp, nil
}
if resp.content_length >= 0 && resp.content_length < len(resp.body) {
resp.body = resp.body[:resp.content_length]
}
resp.body = strings.clone(resp.body)
return "", resp, nil
}
parse_response :: proc(raw: []byte) -> (resp: Response, ok: bool) {
resp.content_length = -1
raw_str := string(raw)
header_end := strings.index(raw_str, "\r\n\r\n")
if header_end < 0 {
return resp, false
}
lines := strings.split(raw_str[:header_end], "\r\n")
defer delete(lines)
if len(lines) == 0 {
return resp, false
}
resp.status = parse_status_code(lines[0])
if resp.status == 0 {
return resp, false
}
for _, i in lines {
if i == 0 do continue
colon := strings.index_byte(lines[i], ':')
if colon < 0 do continue
key := strings.trim_space(lines[i][:colon])
value := strings.trim_space(lines[i][colon + 1:])
switch {
case fold_eq(key, "content-length"):
if n, number_ok := strconv.parse_int(value); number_ok {
resp.content_length = n
}
case fold_eq(key, "transfer-encoding"):
if strings.contains(value, "chunked") {
resp.chunked = true
}
case fold_eq(key, "location"):
resp.location = value
}
}
body_start := header_end + 4
if body_start <= len(raw_str) {
resp.body = raw_str[body_start:]
}
return resp, true
}
parse_status_code :: proc(status_line: string) -> int {
parts := strings.fields(status_line)
defer delete(parts)
if len(parts) < 2 {
return 0
}
code, ok := strconv.parse_int(parts[1])
return code if ok else 0
}
parse_http_url :: proc(
url: string,
) -> (
scheme: string,
host: string,
port: int,
path: string,
ok: bool,
) {
if !strings.has_prefix(url, "https://") && !strings.has_prefix(url, "http://") {
return "", "", 0, "", false
}
https := strings.has_prefix(url, "https://")
rest := url[https ? len("https://") : len("http://"):]
host_and_port := rest
if slash := strings.index_byte(rest, '/'); slash >= 0 {
host_and_port = rest[:slash]
path = rest[slash:]
} else {
path = "/"
}
host = host_and_port
port = https ? DEFAULT_HTTPS_PORT : DEFAULT_HTTP_PORT
if colon := strings.last_index_byte(host_and_port, ':'); colon >= 0 {
parsed, p_ok := strconv.parse_int(host_and_port[colon + 1:])
if !p_ok {
return "", "", 0, "", false
}
host = host_and_port[:colon]
port = parsed
}
if host == "" {
return "", "", 0, "", false
}
return https ? "https" : "http", host, port, path, true
}
resolve_redirect :: proc(base_url, location: string) -> string {
if strings.has_prefix(location, "http://") || strings.has_prefix(location, "https://") {
return strings.clone(location)
}
if !strings.has_prefix(location, "/") {
return ""
}
rest := base_url[len("http://"):]
if slash := strings.index_byte(rest, '/'); slash >= 0 {
rest = rest[:slash]
}
return strings.concatenate([]string{"http://", rest, location})
}
decode_chunked :: proc(data: string) -> (body: string, ok: bool) {
decoded: [dynamic]u8
defer delete(decoded)
at := 0
for at < len(data) {
nl := strings.index_byte(data[at:], '\n')
if nl < 0 do return "", false
size_line := strings.trim_space(data[at:at + nl])
at += nl + 1
if semi := strings.index_byte(size_line, ';'); semi >= 0 {
size_line = size_line[:semi]
}
size, size_ok := strconv.parse_int(size_line, 16)
if !size_ok || size < 0 {
return "", false
}
if size == 0 {
at += 2
break
}
if at + size > len(data) {
return "", false
}
append(&decoded, ..transmute([]u8)data[at:at + size])
at += size + 2
}
if len(decoded) == 0 {
return "", true
}
return strings.clone(string(decoded[:])), true
}
fold_eq :: proc(a, b: string) -> bool {
if len(a) != len(b) {
return false
}
for i in 0 ..< len(a) {
ca := a[i]
cb := b[i]
if ca >= 'A' && ca <= 'Z' do ca += 'a' - 'A'
if cb >= 'A' && cb <= 'Z' do cb += 'a' - 'A'
if ca != cb do return false
}
return true
}
-149
View File
@@ -1,149 +0,0 @@
package http_test
import "core:fmt"
import "core:log"
import "core:net"
import "core:testing"
import "core:thread"
import http "../src"
SERVER_BODY :: "hello world"
SERVER_RESPONSE :=
"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n" +
"Content-Length: 11\r\n" +
"Connection: close\r\n\r\n" +
SERVER_BODY
Serve_Args :: struct {
listener: net.TCP_Socket,
}
serve_proc :: proc(data: rawptr) {
args := (^Serve_Args)(data)
conn, _, err := net.accept_tcp(args.listener)
if err != nil {
log.errorf("test server: accept failed: %v", err)
return
}
defer net.close(conn)
request: [4096]byte
if _, err := net.recv_tcp(conn, request[:]); err != nil {
log.errorf("test server: recv failed: %v", err)
return
}
if _, err := net.send_tcp(conn, transmute([]u8)SERVER_RESPONSE); err != nil {
log.errorf("test server: send failed: %v", err)
return
}
}
@(test)
test_http_get_request :: proc(t: ^testing.T) {
listener, err := net.listen_tcp({net.IP4_Loopback, 0})
if err != nil {
log.errorf("could not start test server: %v", err)
testing.fail(t)
return
}
defer net.close(listener)
bound, berr := net.bound_endpoint(listener)
if berr != nil {
log.errorf("could not read test server port: %v", berr)
testing.fail(t)
return
}
args: Serve_Args = {
listener = listener,
}
server_thread := thread.create_and_start_with_data(&args, serve_proc)
defer thread.destroy(server_thread)
url_buf: [64]byte
url := fmt.bprintf(url_buf[:], "http://127.0.0.1:%d/", bound.port)
log.infof("requesting %s", url)
resp, req_err := http.http_get(url)
if !testing.expectf(t, req_err == nil, "http_get returned an error: %w", req_err) {
return
}
defer delete(resp.body)
testing.expectf(t, resp.status == 200, "expected status 200, got %d", resp.status)
testing.expectf(t, resp.body == SERVER_BODY, "expected %q, got %q", SERVER_BODY, resp.body)
}
@(test)
test_http_parse_url :: proc(t: ^testing.T) {
testURL :: struct {
url: string,
expectedScheme: string,
expectedOK: bool,
expectedHost: string,
expectedPort: int,
expectedPath: string,
}
testURLs := []testURL {
{"http://localhost:8080", "http", true, "localhost", 8080, "/"},
{"http://localhost", "http", true, "localhost", 80, "/"},
{"https://localhost:443", "https", true, "localhost", 443, "/"},
{"https://localhost", "https", true, "localhost", 443, "/"},
{"http://test:6969", "http", true, "test", 6969, "/"},
{
"http://localhost:3000/api/v1/catalogs",
"http",
true,
"localhost",
3000,
"/api/v1/catalogs",
},
}
for tu in testURLs {
scheme, host, port, path, ok := http.parse_http_url(tu.url)
testing.expectf(
t,
ok == tu.expectedOK,
"expected ok from parsing to be %v but got %v",
tu.expectedOK,
ok,
)
testing.expectf(
t,
host == tu.expectedHost,
"expected host url to be '%s' but got '%s'",
tu.expectedHost,
host,
)
testing.expectf(
t,
scheme == tu.expectedScheme,
"expected scheme to be '%s' but got '%s'",
tu.expectedScheme,
scheme,
)
testing.expectf(
t,
port == tu.expectedPort,
"expected port to be %d but got %d",
tu.expectedPort,
port,
)
testing.expectf(
t,
path == tu.expectedPath,
"expected path to be '%s' but got '%s'",
tu.expectedPath,
path,
)
}
}
-44
View File
@@ -1,44 +0,0 @@
package main
import "core:log"
import "core:os"
import dotenv "lib:dotenv/src"
Config :: struct {
api_url: string `env:"API_URL"`,
}
get_config :: proc(env_file: ^string = nil) -> (^Config, ^Error) {
path := ".env"
if env_file != nil && env_file^ != "" {
path = env_file^
} else if from_env, ok := os.lookup_env("GUI_ENV_FILE", context.temp_allocator); ok {
path = from_env
}
log.debugf("getting config from file: %s", path)
env, _ := dotenv.parse_file(path, context.temp_allocator)
defer dotenv.destroy(env, context.temp_allocator)
c := new(Config)
if env == nil {
c.api_url = os.get_env("API_URL", context.temp_allocator)
} else if !dotenv.decode(env, c) {
return nil, new_clone(Error{.Config, "failed to decode .env into Config"})
}
if err := validate_config(c); err != nil {
return nil, err
}
return c, nil
}
validate_config :: proc(c: ^Config) -> (err: ^Error) {
if c.api_url == "" {
err = new_clone(Error{.Config, "'API_URL' is required"})
}
return err
}
+6 -196
View File
@@ -1,11 +1,5 @@
package main package main
import "core:encoding/json"
import "core:fmt"
import "core:log"
import "core:strings"
import http "lib:http/src"
Catalog :: struct { Catalog :: struct {
name: string, name: string,
release: string, release: string,
@@ -27,205 +21,21 @@ APIError :: struct {
message: string, message: string,
} }
endpoint :: proc(base_url, path: string) -> string { get_catalogs :: proc(url: string) -> ([dynamic]Catalog, ^APIError) {
return fmt.tprintf("%s/%s", strings.trim_right(base_url, "/"), strings.trim_left(path, "/")) return nil, nil
} }
api_error :: proc(code: int, message: string) -> ^APIError { get_catalog :: proc(url: string, name: string) -> (^Catalog, ^APIError) {
err := new(APIError) return nil, nil
err.code = code
err.message = message
return err
}
destroy_api_error :: proc(err: ^APIError) {
delete(err.message)
free(err)
}
destroy_catalogs :: proc(catalogs: [dynamic]Catalog) {
for &c in catalogs {
delete(c.name)
delete(c.release)
if c.description != nil {
delete(c.description^)
free(c.description)
}
if c.object_count != nil {
free(c.object_count)
}
}
delete(catalogs)
}
destroy_catalog_objects :: proc(objects: [dynamic]CatalogObject) {
for &o in objects {
delete(o.id)
delete(o.catalog)
delete(o.object_type)
}
delete(objects)
}
request_json_array :: proc(
url: string,
) -> (
arr: json.Array,
val: json.Value,
err: ^APIError,
ok: bool,
) {
resp, req_err := http.http_get(url)
if req_err != nil {
log.errorf("failed getting response from API: %w", req_err)
defer free(req_err)
return nil, json.Null(nil), api_error(req_err.code, strings.clone(req_err.message)), false
}
defer delete(resp.body)
if resp.status != 200 {
log.errorf("response did not return with 200 => %d", resp.status)
return nil,
json.Null(nil),
api_error(resp.status, strings.clone(strings.trim_space(resp.body))),
false
}
v, perr := json.parse_string(resp.body)
if perr != .None {
log.errorf("parsing json response failed: %w", perr)
return nil, json.Null(nil), api_error(int(perr), "invalid JSON in API response"), false
}
a, is_arr := v.(json.Array)
if !is_arr {
log.error("API response was not in JSON array format")
json.destroy_value(v)
return nil, json.Null(nil), api_error(0, "API response was not a JSON array"), false
}
return a, v, nil, true
}
get_catalogs :: proc(base_url: string) -> ([dynamic]Catalog, ^APIError) {
url := endpoint(base_url, "/api/v1/catalogs")
defer delete(url)
log.infof("getting catalogs from url: %s", url)
arr, val, err, ok := request_json_array(url)
if !ok {
return nil, err
}
defer json.destroy_value(val)
catalogs := make([dynamic]Catalog, 0, len(arr))
for item in arr {
if c, cat_ok := catalog_from_json(item); cat_ok {
append(&catalogs, c)
}
}
return catalogs, nil
} }
get_catalog_objects :: proc( get_catalog_objects :: proc(
base_url: string, url: string,
catalog_name: string, catalog_name: string,
region: string = "",
) -> ( ) -> (
[dynamic]CatalogObject, [dynamic]CatalogObject,
^APIError, ^APIError,
) { ) {
sb := strings.builder_make() return nil, nil
defer strings.builder_destroy(&sb)
fmt.sbprintf(&sb, "%s?catalog=%s", endpoint(base_url, "/api/v1/objects"), catalog_name)
if region != "" {
fmt.sbprintf(&sb, "&region=%s", region)
}
fmt.sbprintf(&sb, "&limit=%d", 10_000)
url := strings.to_string(sb)
arr, val, err, ok := request_json_array(url)
if !ok {
return nil, err
}
defer json.destroy_value(val)
objects := make([dynamic]CatalogObject, 0, len(arr))
for item in arr {
if o, obj_ok := catalog_object_from_json(item); obj_ok {
append(&objects, o)
}
}
return objects, nil
}
catalog_from_json :: proc(v: json.Value) -> (c: Catalog, ok: bool) {
obj, is_obj := v.(json.Object)
if !is_obj {
return {}, false
}
c = Catalog {
name = json_string(obj, "name"),
release = json_string(obj, "release"),
}
if d, found := obj["description"]; found {
if s, s_ok := d.(json.String); s_ok {
c.description = new_clone(strings.clone(s))
}
}
if n, n_ok := json_u64(obj, "object_count"); n_ok {
c.object_count = new_clone(n)
}
return c, true
}
catalog_object_from_json :: proc(v: json.Value) -> (o: CatalogObject, ok: bool) {
obj, is_obj := v.(json.Object)
if !is_obj {
return {}, false
}
return CatalogObject {
id = json_string(obj, "id"),
catalog = json_string(obj, "catalog"),
object_type = json_string(obj, "object_type"),
ra = json_f64(obj, "ra"),
dec = json_f64(obj, "dec"),
redshift = json_f64(obj, "redshift"),
},
true
}
json_string :: proc(obj: json.Object, key: string) -> string {
if v, found := obj[key]; found {
if s, ok := v.(json.String); ok {
return strings.clone(s)
}
}
return ""
}
json_f64 :: proc(obj: json.Object, key: string) -> f64 {
if v, found := obj[key]; found {
#partial switch n in v {
case json.Integer:
return f64(n)
case json.Float:
return n
}
}
return 0
}
json_u64 :: proc(obj: json.Object, key: string) -> (u64, bool) {
if v, found := obj[key]; found {
#partial switch n in v {
case json.Integer:
return u64(n), true
case json.Float:
return u64(n), true
}
}
return 0, false
} }
-20
View File
@@ -1,20 +0,0 @@
package main
import "core:fmt"
import "core:strings"
ErrorType :: enum {
Config,
API,
}
Error :: struct {
type: ErrorType,
message: string,
}
format_error :: proc(err: ^Error) -> string {
sb := strings.builder_make(context.temp_allocator)
return fmt.sbprintf(&sb, "[%s] => %s", err.type, err.message)
}
+10 -116
View File
@@ -1,19 +1,14 @@
package main package main
import "base:runtime"
import "core:fmt"
import "core:log"
import "core:math" import "core:math"
import "core:math/rand" import "core:math/rand"
import "core:os"
import "core:strings"
import rl "vendor:raylib" import rl "vendor:raylib"
WIDTH :: 1280 WIDTH :: 1280
HEIGHT :: 720 HEIGHT :: 720
// Number of procedurally generated points standing in for the DESI catalog. // Number of procedurally generated points standing in for the DESI catalog.
// This is replaced by real survey data once a catalog is loaded. // This is replaced by real survey data once ingestion lands.
POINT_COUNT :: 4_000 POINT_COUNT :: 4_000
WORLD_RADIUS :: f32(500.0) WORLD_RADIUS :: f32(500.0)
@@ -22,44 +17,13 @@ Galaxy :: struct {
color: rl.Color, color: rl.Color,
} }
Screen :: enum {
Start,
Universe,
}
App :: struct {
screen: Screen,
api_url: strings.Builder,
api_url_focused: bool,
region: strings.Builder,
region_focused: bool,
catalogs: [dynamic]Catalog,
selected_catalog: int,
catalog_scroll: int,
status: strings.Builder,
loaded_catalog: string,
loaded_region: string,
}
camera: rl.Camera3D camera: rl.Camera3D
app: App
universe: [dynamic]Galaxy universe: [dynamic]Galaxy
// Deterministic generator so the placeholder sky is stable between runs. // Deterministic generator so the placeholder sky is stable between runs.
rng: rand.Default_Random_State rng: rand.Default_Random_State
main :: proc() { main :: proc() {
c: ^Config
err: ^Error
context.logger = log.create_console_logger()
defer log.destroy_console_logger(context.logger)
s := os.get_env("GUI_ENV_FILE", context.temp_allocator)
if c, err = get_config(&s); err != nil {
panic(format_error(err))
}
rng = rand.create(0xDE51_0000) rng = rand.create(0xDE51_0000)
context.random_generator = rand.default_random_generator(&rng) context.random_generator = rand.default_random_generator(&rng)
@@ -69,47 +33,15 @@ main :: proc() {
rl.SetTargetFPS(60) rl.SetTargetFPS(60)
camera = make_camera() camera = make_camera()
app = make_app(c) make_universe(&universe, POINT_COUNT)
defer destroy_app(&app) defer delete(universe)
for !rl.WindowShouldClose() { for !rl.WindowShouldClose() {
switch app.screen { update()
case .Start: draw()
start_screen_update()
start_screen_draw()
case .Universe:
update()
draw()
}
} }
} }
make_app :: proc(c: ^Config) -> App {
a := App {
screen = .Start,
selected_catalog = -1,
}
a.api_url = strings.builder_make()
a.region = strings.builder_make()
a.status = strings.builder_make()
strings.write_string(&a.api_url, c.api_url)
strings.write_string(
&a.status,
"Set the API URL, press Refresh, pick a catalog, then Explore.",
)
return a
}
destroy_app :: proc(a: ^App) {
strings.builder_destroy(&a.api_url)
strings.builder_destroy(&a.region)
strings.builder_destroy(&a.status)
destroy_catalogs(a.catalogs)
delete(a.loaded_catalog)
delete(a.loaded_region)
delete(universe)
}
make_camera :: proc() -> rl.Camera3D { make_camera :: proc() -> rl.Camera3D {
return { return {
position = {0, 220, 220}, position = {0, 220, 220},
@@ -129,29 +61,6 @@ make_universe :: proc(u: ^[dynamic]Galaxy, count: int) {
} }
} }
make_universe_from_objects :: proc(u: ^[dynamic]Galaxy, objects: []CatalogObject) {
clear(u)
reserve(u, len(objects))
for obj in objects {
pos := ra_dec_to_pos(f32(obj.ra), f32(obj.dec), f32(obj.redshift))
append(u, Galaxy{position = pos, color = color_for_position(pos)})
}
}
// Maps an equatorial position (ra/dec in degrees) plus redshift to a point in
// the scene: shells telescope outward with redshift.
ra_dec_to_pos :: proc(ra, dec, redshift: f32) -> rl.Vector3 {
theta := math.to_radians(ra)
phi := math.to_radians(dec)
t := math.clamp(redshift * 0.6, 0.05, 1.0)
r := WORLD_RADIUS * t
return {
r * math.cos(phi) * math.cos(theta),
r * math.sin(phi),
r * math.cos(phi) * math.sin(theta),
}
}
// Uniformly distributed random point inside the scene's bounding sphere. // Uniformly distributed random point inside the scene's bounding sphere.
random_sphere_point :: proc(radius: f32) -> rl.Vector3 { random_sphere_point :: proc(radius: f32) -> rl.Vector3 {
for { for {
@@ -176,11 +85,6 @@ color_for_position :: proc(p: rl.Vector3) -> rl.Color {
update :: proc() { update :: proc() {
// Orbital camera: drag to rotate, scroll to zoom, right-drag / shift to pan. // Orbital camera: drag to rotate, scroll to zoom, right-drag / shift to pan.
rl.UpdateCamera(&camera, .ORBITAL) rl.UpdateCamera(&camera, .ORBITAL)
// Return to the start screen to switch catalog / region without restarting.
if rl.IsKeyPressed(.TAB) {
app.screen = .Start
}
} }
draw :: proc() { draw :: proc() {
@@ -199,21 +103,11 @@ draw :: proc() {
} }
rl.DrawFPS(10, 10) rl.DrawFPS(10, 10)
ui_draw_text("DESI Explorer - drag to rotate, scroll to zoom", 10, 34, 18, rl.RAYWHITE) rl.DrawText(
"DESI Explorer — drag to rotate, scroll to zoom",
buf: [128]u8
if app.loaded_catalog != "" {
ui_draw_text(fmt.bprintf(buf[:], "Catalog: %s", app.loaded_catalog), 10, 60, 18, rl.YELLOW)
}
if app.loaded_region != "" {
ui_draw_text(fmt.bprintf(buf[:], "Region: %s", app.loaded_region), 10, 82, 18, rl.YELLOW)
}
ui_draw_text(
"Press TAB to return to the start screen",
10, 10,
i32(rl.GetScreenHeight()) - 28, 34,
16, 18,
{120, 120, 140, 255}, rl.RAYWHITE,
) )
} }
-196
View File
@@ -1,196 +0,0 @@
package main
import "core:fmt"
import "core:strings"
import rl "vendor:raylib"
CATALOG_ROW_H :: 30
start_screen_update :: proc() {
api_rect := rl.Rectangle{60, 112, 460, 34}
region_rect := rl.Rectangle{60, 186, 460, 34}
text_input_update(&app.api_url, &app.api_url_focused, api_rect)
text_input_update(&app.region, &app.region_focused, region_rect)
if ui_button_clicked(rl.Rectangle{60, 230, 170, 38}) {
refresh_catalogs()
}
list_rect := rl.Rectangle{60, 300, 520, 230}
if clicked := catalog_list_update(list_rect); clicked >= 0 {
app.selected_catalog = clicked
set_statusf("Selected catalog: %s", app.catalogs[clicked].name)
}
if ui_button_clicked(rl.Rectangle{60, 548, 150, 38}) {
if explore() {
app.screen = .Universe
}
}
}
start_screen_draw :: proc() {
rl.BeginDrawing()
defer rl.EndDrawing()
rl.ClearBackground({8, 10, 20, 255})
ui_draw_text("DESI Explorer", 60, 36, 40, rl.WHITE)
ui_draw_text("API URL", 60, 92, 16, {150, 160, 190, 255})
text_input_draw(&app.api_url, app.api_url_focused, rl.Rectangle{60, 112, 460, 34})
ui_draw_text("Region (optional)", 60, 166, 16, {150, 160, 190, 255})
text_input_draw(&app.region, app.region_focused, rl.Rectangle{60, 186, 460, 34})
ui_button(rl.Rectangle{60, 230, 170, 38}, "Refresh catalogs")
ui_draw_text("Catalogs", 60, 280, 16, {150, 160, 190, 255})
catalog_list_draw(rl.Rectangle{60, 300, 520, 230})
ui_button(rl.Rectangle{60, 548, 150, 38}, "Explore")
ui_draw_text(strings.to_string(app.status), 60, i32(rl.GetScreenHeight()) - 36, 16, {200, 200, 220, 255})
}
set_status :: proc(msg: string) {
strings.builder_reset(&app.status)
strings.write_string(&app.status, msg)
}
set_statusf :: proc(format: string, args: ..any) {
strings.builder_reset(&app.status)
fmt.sbprintf(&app.status, format, ..args)
}
refresh_catalogs :: proc() {
destroy_catalogs(app.catalogs)
app.catalogs = nil
app.selected_catalog = -1
base := strings.trim_space(strings.to_string(app.api_url))
if base == "" {
set_status("Enter an API URL first.")
return
}
catalogs, err := get_catalogs(base)
if err != nil {
set_statusf("Failed to load catalogs: %s", err.message)
destroy_api_error(err)
return
}
app.catalogs = catalogs
set_statusf("Loaded %d catalog(s). Select one and press Explore.", len(catalogs))
}
explore :: proc() -> bool {
if app.selected_catalog < 0 || app.selected_catalog >= len(app.catalogs) {
set_status("Select a catalog first.")
return false
}
base := strings.trim_space(strings.to_string(app.api_url))
region := strings.trim_space(strings.to_string(app.region))
if base == "" {
set_status("Enter an API URL first.")
return false
}
c := app.catalogs[app.selected_catalog]
objects, err := get_catalog_objects(base, c.name, region)
if err != nil {
set_statusf("Failed to load objects: %s", err.message)
destroy_api_error(err)
return false
}
delete(app.loaded_catalog)
delete(app.loaded_region)
app.loaded_catalog = strings.clone(c.name)
app.loaded_region = strings.clone(region)
if len(objects) == 0 {
make_universe(&universe, POINT_COUNT)
set_status("No objects returned - showing placeholder sky.")
} else {
make_universe_from_objects(&universe, objects[:])
set_statusf("Loaded %d object(s).", len(objects))
}
destroy_catalog_objects(objects)
return true
}
visible_row_count :: proc(rec: rl.Rectangle) -> int {
return max(1, int(rec.height) / CATALOG_ROW_H)
}
row_rect :: proc(rec: rl.Rectangle, index, scroll_offset: int) -> rl.Rectangle {
y := rec.y + f32((index - scroll_offset) * CATALOG_ROW_H)
return rl.Rectangle{rec.x, y, rec.width, CATALOG_ROW_H}
}
catalog_list_update :: proc(rec: rl.Rectangle) -> int {
mouse := rl.GetMousePosition()
if rl.CheckCollisionPointRec(mouse, rec) {
wheel := rl.GetMouseWheelMove()
max_scroll := max(0, len(app.catalogs) - visible_row_count(rec))
app.catalog_scroll = max(0, min(app.catalog_scroll - int(wheel), max_scroll))
}
clicked := -1
if len(app.catalogs) == 0 {
return clicked
}
visible := visible_row_count(rec)
for i in app.catalog_scroll ..< min(len(app.catalogs), app.catalog_scroll + visible) {
row := row_rect(rec, i, app.catalog_scroll)
if rl.CheckCollisionPointRec(mouse, row) && rl.IsMouseButtonPressed(.LEFT) {
clicked = i
}
}
return clicked
}
catalog_list_draw :: proc(rec: rl.Rectangle) {
rl.DrawRectangleRec(rec, {12, 14, 22, 255})
rl.DrawRectangleLinesEx(rec, 1, {56, 60, 82, 255})
if len(app.catalogs) == 0 {
ui_draw_text("No catalogs loaded - press Refresh", i32(rec.x) + 12, i32(rec.y) + 12, 16, {120, 120, 140, 255})
return
}
mouse := rl.GetMousePosition()
visible := visible_row_count(rec)
for i in app.catalog_scroll ..< min(len(app.catalogs), app.catalog_scroll + visible) {
row := row_rect(rec, i, app.catalog_scroll)
if i == app.selected_catalog {
rl.DrawRectangleRec(row, {44, 56, 92, 255})
rl.DrawRectangleLinesEx(row, 1, {120, 160, 235, 255})
} else if rl.CheckCollisionPointRec(mouse, row) {
rl.DrawRectangleRec(row, {26, 34, 56, 255})
}
buf: [256]u8
ui_draw_text(
catalog_display(buf[:], app.catalogs[i]),
i32(rec.x) + 12,
i32(row.y) + 6,
16,
rl.RAYWHITE,
)
}
}
catalog_display :: proc(buf: []u8, c: Catalog) -> string {
if c.description != nil {
return fmt.bprintf(buf, "%s (%s)", c.name, c.description^)
}
if c.release != "" {
return fmt.bprintf(buf, "%s (%s)", c.name, c.release)
}
return fmt.bprintf(buf, "%s", c.name)
}
-90
View File
@@ -1,90 +0,0 @@
package main
import c "core:c"
import "core:strings"
import "core:unicode/utf8"
import rl "vendor:raylib"
MAX_INPUT_LEN :: 256
to_cstring_buf :: proc(buf: []u8, s: string) -> cstring {
n := min(len(s), len(buf) - 1)
copy(buf[:n], s[:n])
buf[n] = 0
return cstring(&buf[0])
}
ui_draw_text :: proc(text: string, x, y, size: c.int, color: rl.Color) {
buf: [1024]u8
rl.DrawText(to_cstring_buf(buf[:], text), x, y, size, color)
}
text_input_update :: proc(b: ^strings.Builder, focused: ^bool, rec: rl.Rectangle) {
mouse := rl.GetMousePosition()
if rl.CheckCollisionPointRec(mouse, rec) && rl.IsMouseButtonPressed(.LEFT) {
focused^ = true
} else if rl.IsMouseButtonPressed(.LEFT) {
focused^ = false
}
if !focused^ {
return
}
for r := rl.GetCharPressed(); r != 0; {
if len(b.buf) < MAX_INPUT_LEN {
strings.write_rune(b, r)
}
r = rl.GetCharPressed()
}
if rl.IsKeyPressed(.BACKSPACE) {
s := strings.to_string(b^)
_, rune_len := utf8.decode_last_rune(s)
if rune_len > 0 && len(b.buf) >= rune_len {
resize(&b.buf, len(b.buf) - rune_len)
}
}
}
text_input_draw :: proc(b: ^strings.Builder, focused: bool, rec: rl.Rectangle) {
text := strings.to_string(b^)
if focused {
rl.DrawRectangleRec(rec, {28, 34, 56, 255})
rl.DrawRectangleLinesEx(rec, 2, {90, 160, 240, 255})
} else {
rl.DrawRectangleRec(rec, {16, 18, 30, 255})
rl.DrawRectangleLinesEx(rec, 1, {70, 76, 96, 255})
}
buf: [MAX_INPUT_LEN + 1]u8
cstr := to_cstring_buf(buf[:], text)
rl.DrawText(cstr, i32(rec.x) + 8, i32(rec.y) + 8, 18, rl.RAYWHITE)
if focused {
cw := rl.MeasureText(cstr, 18)
rl.DrawRectangle(i32(rec.x) + 8 + cw + 1, i32(rec.y) + 8, 2, 20, rl.SKYBLUE)
}
}
ui_button_clicked :: proc(rec: rl.Rectangle) -> bool {
mouse := rl.GetMousePosition()
return rl.CheckCollisionPointRec(mouse, rec) && rl.IsMouseButtonPressed(.LEFT)
}
ui_button :: proc(rec: rl.Rectangle, label: string) {
mouse := rl.GetMousePosition()
hovered := rl.CheckCollisionPointRec(mouse, rec)
if hovered {
rl.DrawRectangleRec(rec, {48, 66, 118, 255})
} else {
rl.DrawRectangleRec(rec, {30, 38, 66, 255})
}
rl.DrawRectangleLinesEx(rec, 1, {92, 104, 134, 255})
buf: [128]u8
cstr := to_cstring_buf(buf[:], label)
w := rl.MeasureText(cstr, 18)
rl.DrawText(cstr, i32(rec.x) + (i32(rec.width) - w) / 2, i32(rec.y) + 10, 18, rl.RAYWHITE)
}
+37 -50
View File
@@ -1,17 +1,17 @@
module desi-explorer-infra module desi-explorer-infra
go 1.26.6 go 1.26
require ( require (
github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.34.0 github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.31.1
github.com/pulumi/pulumi/sdk/v3 v3.261.0 github.com/pulumi/pulumi/sdk/v3 v3.244.0
) )
require ( require (
dario.cat/mergo v1.0.0 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/agext/levenshtein v1.2.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
@@ -24,7 +24,7 @@ require (
github.com/charmbracelet/bubbletea v1.3.10 // indirect github.com/charmbracelet/bubbletea v1.3.10 // indirect
github.com/charmbracelet/colorprofile v0.4.3 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/lipgloss v1.1.0 // indirect github.com/charmbracelet/lipgloss v1.1.0 // indirect
github.com/charmbracelet/x/ansi v0.11.8 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/cheggaaa/pb v1.0.29 // indirect github.com/cheggaaa/pb v1.0.29 // indirect
@@ -32,42 +32,34 @@ require (
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect github.com/cloudflare/circl v1.6.3 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/danieljoos/wincred v1.2.3 // indirect
github.com/deckarep/golang-set/v2 v2.5.0 // indirect
github.com/djherbis/times v1.5.0 // indirect github.com/djherbis/times v1.5.0 // indirect
github.com/ebitengine/purego v0.10.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/gcfg/v2 v2.0.2 // indirect
github.com/go-git/go-billy/v5 v5.9.0 // indirect github.com/go-git/go-billy/v5 v5.9.0 // indirect
github.com/go-git/go-billy/v6 v6.0.0-alpha.1 // indirect
github.com/go-git/go-git/v5 v5.19.1 // indirect github.com/go-git/go-git/v5 v5.19.1 // indirect
github.com/go-git/go-git/v6 v6.0.0-alpha.4 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/logr v1.4.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/godbus/dbus/v5 v5.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.2.5 // indirect github.com/golang/glog v1.2.5 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/go-tpm v0.9.8 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-version v1.9.0 // indirect github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/hashicorp/hcl/v2 v2.22.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.18.7 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.28 // indirect github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -95,38 +87,33 @@ require (
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v1.0.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zalando/go-keyring v0.2.8 // indirect github.com/zclconf/go-cty v1.13.2 // indirect
github.com/zclconf/go-cty v1.16.3 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/featuregate v1.65.0 // indirect go.opentelemetry.io/collector/featuregate v1.53.0 // indirect
go.opentelemetry.io/collector/pdata v1.65.0 // indirect go.opentelemetry.io/collector/pdata v1.53.0 // indirect
go.opentelemetry.io/contrib/bridges/otelslog v0.20.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel v1.45.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect
go.opentelemetry.io/otel/log v0.21.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.45.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.opentelemetry.io/otel/sdk v1.45.0 // indirect
go.opentelemetry.io/otel/sdk/log v0.21.0 // indirect
go.opentelemetry.io/otel/trace v1.45.0 // indirect
go.opentelemetry.io/proto/otlp v1.11.0 // indirect
go.uber.org/atomic v1.11.0 // indirect go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.55.0 // indirect golang.org/x/crypto v0.50.0 // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
golang.org/x/mod v0.38.0 // indirect golang.org/x/mod v0.35.0 // indirect
golang.org/x/net v0.58.0 // indirect golang.org/x/net v0.53.0 // indirect
golang.org/x/sync v0.22.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.47.0 // indirect golang.org/x/sys v0.43.0 // indirect
golang.org/x/term v0.45.0 // indirect golang.org/x/term v0.42.0 // indirect
golang.org/x/text v0.41.0 // indirect golang.org/x/text v0.36.0 // indirect
golang.org/x/tools v0.48.0 // indirect golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect
google.golang.org/grpc v1.83.1 // indirect google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.12 // indirect google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/frand v1.5.1 // indirect lukechampine.com/frand v1.5.1 // indirect
-98
View File
@@ -9,8 +9,6 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
@@ -41,8 +39,6 @@ github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoF
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
@@ -58,17 +54,11 @@ github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJ
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/deckarep/golang-set/v2 v2.5.0 h1:hn6cEZtQ0h3J8kFrHR/NrzyOoTnjgW1+FmNJzQ7y/sA=
github.com/deckarep/golang-set/v2 v2.5.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU=
github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0=
github.com/ebitengine/purego v0.10.2 h1:W809HbnvzAxgdm+aOvlSekrM16wGCdT/e76+9tS7gzE=
github.com/ebitengine/purego v0.10.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
@@ -82,27 +72,17 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA= github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw= github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
github.com/go-git/go-billy/v6 v6.0.0-alpha.1 h1:xVjAR4oUvrKy7/Xuw/lLlV3gkxR3KO2H8W+MamuVVsQ=
github.com/go-git/go-billy/v6 v6.0.0-alpha.1/go.mod h1:eaCUpHbedW7//EwcYmUDfJe2N6sJC9O12AT0OTqJR1E=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00= github.com/go-git/go-git/v5 v5.19.1 h1:nX27AnaU43/K5bKktKwgBmR9lawoYVe1Ckg0rgzzN00=
github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ= github.com/go-git/go-git/v5 v5.19.1/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
github.com/go-git/go-git/v6 v6.0.0-alpha.4 h1:aDTc2UGanmaE7FkGLSlBEB9nohMnQ+RKXcfq/D+esDQ=
github.com/go-git/go-git/v6 v6.0.0-alpha.4/go.mod h1:4ODa/G7hPWrh4Y+7lmt59Ij3zW38IEfvRoAZxLYYBhc=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -114,15 +94,11 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
github.com/google/go-tpm v0.9.8/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -132,12 +108,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=
github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -146,15 +118,11 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
@@ -166,8 +134,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
@@ -175,15 +141,11 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-runewidth v0.0.28 h1:rPyg2ybwEKPebvpzVWe1gKBkH8EQFkxO4Y0hjBeLaBU=
github.com/mattn/go-runewidth v0.0.28/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
@@ -225,12 +187,8 @@ github.com/pulumi/esc v0.24.0 h1:sCtiB0qbyrlU1ZNzJn4dTLYiChl8xeCBFbHWl1YoXJg=
github.com/pulumi/esc v0.24.0/go.mod h1:eCOOkcDJS6eooGwdE4/E0+pOsvUWG254+KBmPCFwJpA= github.com/pulumi/esc v0.24.0/go.mod h1:eCOOkcDJS6eooGwdE4/E0+pOsvUWG254+KBmPCFwJpA=
github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.31.1 h1:Hg9RK9zqIU9kFbD5KeiON06gPP7cLgS68jvsgMBmPgw= github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.31.1 h1:Hg9RK9zqIU9kFbD5KeiON06gPP7cLgS68jvsgMBmPgw=
github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.31.1/go.mod h1:BAWI9R3JEEGOp1JlXLPSZKwBGANSrPGUWKtMnS5w5qw= github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.31.1/go.mod h1:BAWI9R3JEEGOp1JlXLPSZKwBGANSrPGUWKtMnS5w5qw=
github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.34.0 h1:r00p17ecoDVWqBg38/Is/9jU/JsWLPxnWzhfYqKitx0=
github.com/pulumi/pulumi-kubernetes/sdk/v4 v4.34.0/go.mod h1:/klrd3ib4AMRlFMjrxkv5r76x3Bdr68wIMEKfRkRzHU=
github.com/pulumi/pulumi/sdk/v3 v3.244.0 h1:oyQ9bwDE58wrQOqS70JrojJSlNoLcVhVcQgK4yusgcg= github.com/pulumi/pulumi/sdk/v3 v3.244.0 h1:oyQ9bwDE58wrQOqS70JrojJSlNoLcVhVcQgK4yusgcg=
github.com/pulumi/pulumi/sdk/v3 v3.244.0/go.mod h1:BPWWuYPXcPH5YbXGoyy9Rrfa+evrh6IdM51AjDhcDpM= github.com/pulumi/pulumi/sdk/v3 v3.244.0/go.mod h1:BPWWuYPXcPH5YbXGoyy9Rrfa+evrh6IdM51AjDhcDpM=
github.com/pulumi/pulumi/sdk/v3 v3.261.0 h1:1C2V6bxzO9UBe91PRebO/zeiUIQ2C05eRNkVFvJuuqc=
github.com/pulumi/pulumi/sdk/v3 v3.261.0/go.mod h1:xlOo55i2hj5tDyMY8diTS5LfFAn2sFew+50d67sVNdg=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -267,66 +225,34 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=
github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0=
github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0=
github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk=
github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/collector/featuregate v1.53.0 h1:cgjXdtl7jezWxq6V0eohe/JqjY4PBotZGb5+bTR2OJw= go.opentelemetry.io/collector/featuregate v1.53.0 h1:cgjXdtl7jezWxq6V0eohe/JqjY4PBotZGb5+bTR2OJw=
go.opentelemetry.io/collector/featuregate v1.53.0/go.mod h1:PS7zY/zaCb28EqciePVwRHVhc3oKortTFXsi3I6ee4g= go.opentelemetry.io/collector/featuregate v1.53.0/go.mod h1:PS7zY/zaCb28EqciePVwRHVhc3oKortTFXsi3I6ee4g=
go.opentelemetry.io/collector/featuregate v1.65.0 h1:Dh+uYVB+POc5DTebZRWjtKJolGhevkiIpbHn+zhkq2o=
go.opentelemetry.io/collector/featuregate v1.65.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU=
go.opentelemetry.io/collector/internal/testutil v0.147.0 h1:DFlRxBRp23/sZnpTITK25yqe0d56yNvK+63IaWc6OsU= go.opentelemetry.io/collector/internal/testutil v0.147.0 h1:DFlRxBRp23/sZnpTITK25yqe0d56yNvK+63IaWc6OsU=
go.opentelemetry.io/collector/internal/testutil v0.147.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= go.opentelemetry.io/collector/internal/testutil v0.147.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE=
go.opentelemetry.io/collector/pdata v1.53.0 h1:DlYDbRwammEZaxDZHINx5v0n8SEOVNniPbi6FRTlVkA= go.opentelemetry.io/collector/pdata v1.53.0 h1:DlYDbRwammEZaxDZHINx5v0n8SEOVNniPbi6FRTlVkA=
go.opentelemetry.io/collector/pdata v1.53.0/go.mod h1:LRSYGNjKXaUrZEwZv3Yl+8/zV2HmRGKXW62zB2bysms= go.opentelemetry.io/collector/pdata v1.53.0/go.mod h1:LRSYGNjKXaUrZEwZv3Yl+8/zV2HmRGKXW62zB2bysms=
go.opentelemetry.io/collector/pdata v1.65.0 h1:6bQ3sIrEzOdapetxYFjdCns90kKXg1qCoIZ3la1aR5E=
go.opentelemetry.io/collector/pdata v1.65.0/go.mod h1:r5vRY0p7nZcEif06twUW09Sf6vaNsyPzij+EpwI/xeI=
go.opentelemetry.io/contrib/bridges/otelslog v0.20.0 h1:oEl2Pw/i4OQwhAuda2pAHFAcOMivA+Xa+iTccBfab/g=
go.opentelemetry.io/contrib/bridges/otelslog v0.20.0/go.mod h1:yMSQaiiq5dpfrSJCYLBcqFeJkFFI67seT4ngvx6jfVo=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU=
go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0 h1:WseeVYf5dJZTsyPiyW5L14k5qsSibqXAMTSiFEDiWr0=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.21.0/go.mod h1:SiLZnQS6Qk2eCpvr2CH/XMAOa64TWGXxEZJZCpD2Lmc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0 h1:QRefszxJmfPdjXUUm3j6iDzY03mTPXMjqErFqQ67vUg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.45.0/go.mod h1:Tiz03lTBVBrm7eWZBOidzEaYaJa8tjwGUGv6d8mlTyk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0 h1:fG5MCxGz8+2VtrN/WgqSpJFctVz24gpxj8CxkKmc8Ww=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.45.0/go.mod h1:BmAYTn+3ysbRe+IU2msxmf5Rx3g6DHvex+tWI3LdhYI=
go.opentelemetry.io/otel/log v0.21.0 h1:SLsVDGmtyBrdw8/a2Z0bOIxou/+bN4z56GebH7T0LvA=
go.opentelemetry.io/otel/log v0.21.0/go.mod h1:iReetQrZL9Wyg84cCkOoCmqDHS5RCFfyxC7J+r8fn8g=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M=
go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw=
go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA=
go.opentelemetry.io/otel/sdk/log v0.21.0 h1:QsE7XSR0ktQdKmRKGnR+f1ObGF32WG+7MER/P9KgmYc=
go.opentelemetry.io/otel/sdk/log v0.21.0/go.mod h1:m9mApjCoD2/1QuKCAptjv+BrG9WKOvQLVdNx+iBldTo=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag=
go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc=
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
go.opentelemetry.io/proto/slim/otlp v1.9.0 h1:fPVMv8tP3TrsqlkH1HWYUpbCY9cAIemx184VGkS6vlE= go.opentelemetry.io/proto/slim/otlp v1.9.0 h1:fPVMv8tP3TrsqlkH1HWYUpbCY9cAIemx184VGkS6vlE=
go.opentelemetry.io/proto/slim/otlp v1.9.0/go.mod h1:xXdeJJ90Gqyll+orzUkY4bOd2HECo5JofeoLpymVqdI= go.opentelemetry.io/proto/slim/otlp v1.9.0/go.mod h1:xXdeJJ90Gqyll+orzUkY4bOd2HECo5JofeoLpymVqdI=
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0 h1:o13nadWDNkH/quoDomDUClnQBpdQQ2Qqv0lQBjIXjE8= go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.2.0 h1:o13nadWDNkH/quoDomDUClnQBpdQQ2Qqv0lQBjIXjE8=
@@ -346,8 +272,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@@ -356,8 +280,6 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -366,15 +288,11 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -391,20 +309,14 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -413,8 +325,6 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -423,20 +333,12 @@ gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c h1:OyQPd6I3pN/9gDxz6L13kYGJgqkpdrAohJRBeXyxlgI= google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c h1:OyQPd6I3pN/9gDxz6L13kYGJgqkpdrAohJRBeXyxlgI=
google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c/go.mod h1:X2gu9Qwng7Nn009s/r3RUxqkzQNqOrAy79bluY7ojIg= google.golang.org/genproto/googleapis/api v0.0.0-20260311181403-84a4fc48630c/go.mod h1:X2gu9Qwng7Nn009s/r3RUxqkzQNqOrAy79bluY7ojIg=
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI=
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c h1:xgCzyF2LFIO/0X2UAoVRiXKU5Xg6VjToG4i2/ecSswk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c h1:xgCzyF2LFIO/0X2UAoVRiXKU5Xg6VjToG4i2/ecSswk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688 h1:cYNAzI2sUwhmCcoj9TxvihSrqsxt6uIkj3rDRhSDmW4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260819154853-08b0e4226688/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
@@ -59,13 +59,11 @@ get_catalog_objects :: proc(url: string, catalog_name: string) // nil
## Data flow gap ## Data flow gap
```mermaid ```
flowchart LR [DESI catalog store] --(future)--> [Rust/axum API] --(nothing today)--> [Odin + raylib GUI]
A["DESI catalog store"] ^ ^
B["Rust / axum API<br/><i>serde JSON models</i>"] | serde JSON models | hand-mirrored structs
C["Odin + raylib GUI<br/><i>hand-mirrored structs<br/>stubs, never used</i>"] | | (stubs, never used)
A -. "future ingestion" .-> B
B --x|"nothing today"| C
``` ```
There is **no live data flow**. The API currently returns JSON placeholders; the There is **no live data flow**. The API currently returns JSON placeholders; the
@@ -118,16 +116,17 @@ response protocol with a cheap binary payload would fit this well.
## High-level target architecture ## High-level target architecture
```mermaid ```
flowchart TB catalog.fbs (single source of truth, checked into repo)
S["catalog.fbs<br/><i>single source of truth<br/>checked into repo</i>"] |
R["flatc --rust"] +--------+---------+
C["flatcc --c<br/><i>or hand-rolled Odin reader</i>"] | |
API["api/ · Rust"] flatc --rust flatcc --c (or hand-rolled Odin reader)
GUI["gui/ · Odin + raylib"] | |
S --> R --> API api/ (Rust) gui/ (Odin + raylib)
S --> C --> GUI | ^
API <-->|"HTTP / WebSocket<br/>framed FlatBuffer binary stream"| GUI | HTTP / WebSocket (framed FlatBuffer binary stream)
+------------------+
``` ```
- One schema file. Two generators. Byte-for-byte identical wire format. - One schema file. Two generators. Byte-for-byte identical wire format.
@@ -105,28 +105,19 @@ These are the "thou shalt" rules for keeping buffers compatible:
## Reading a buffer (conceptual) ## Reading a buffer (conceptual)
Buffer layout:
```mermaid
flowchart LR
subgraph BUF["bytes: &[u8]"]
O["uoffset<br/>root table offset"]
FI["file_identifier<br/>(optional)"]
D["tables · vtables · data"]
end
O --> FI --> D
``` ```
bytes: &[u8]
Access sequence — each field is a few offset dereferences and a read: ┌─────────────────────────────┐
│ uoffset (root table offset) │
│ file_identifier (optional) │
│ ... tables, vtables, data ...│
└─────────────────────────────┘
```mermaid root = follow(bytes) // jump to root table via uoffset
flowchart TD vtable = root - root.vtable_off // locate vtable for this table
R["root = follow(bytes)<br/>jump to root table via uoffset"] field_ra = vtable.slot_ra != 0 // present?
V["vtable = root root.vtable_off<br/>locate vtable for this table"] if present: ra = read_f64(bytes, root + slot_ra)
Q{"field slot present?"}
R --> V --> Q
Q -- "no" --> DEF["use schema default"]
Q -- "yes" --> RD["ra = read_f64(bytes, root + slot_ra)"]
``` ```
There is **no parsing loop**. Each accessor is a few offset dereferences and a There is **no parsing loop**. Each accessor is a few offset dereferences and a
@@ -196,18 +196,6 @@ Notes:
## Building buffers efficiently (Rust specifics) ## Building buffers efficiently (Rust specifics)
Build order is **back-to-front** (children before parents):
```mermaid
flowchart TB
A["create_string / create_vector<br/>children first"]
B["create nested child tables"]
C["create parent table<br/>ObjectBatch::create(&args)"]
D["builder.finish(root, Some(&#34;DESI&#34;))"]
E["Bytes::copy_from_slice(fbb.finished_data())<br/>→ HTTP / WebSocket response"]
A --> B --> C --> D --> E
```
- `FlatBufferBuilder::with_capacity(n)` pre-allocates; `reset()` reuses the - `FlatBufferBuilder::with_capacity(n)` pre-allocates; `reset()` reuses the
buffer across messages. In a loop streaming batches, create one builder, reuse buffer across messages. In a loop streaming batches, create one builder, reuse
it — avoid repeated reallocation. it — avoid repeated reallocation.
@@ -17,21 +17,6 @@ has two C-related access points:
schema plus a small `libflatccrt.a` runtime. Works via the C ABI, so Odin's schema plus a small `libflatccrt.a` runtime. Works via the C ABI, so Odin's
`foreign import` can consume it. `foreign import` can consume it.
Choosing a path:
```mermaid
flowchart TD
NAT{"primary target is native desktop?"}
NAT -- "yes" --> CGO{"want to avoid C in the build?"}
CGO -- "yes" --> PATHA["Path B · pure-Odin reader<br/>hand-rolled, no C dependency"]
CGO -- "no" --> PATHA
CGO -- "prefer proven lib / less maintenance" --> PATHC["Path A · FFI to FlatCC<br/>bind generated C headers"]
PATHC --> REUSE["Path C · OdinArrow reuse<br/>or borrow its decode patterns"]
NAT -- "no · browser/WASM" --> PATHD["Path D · TS/JS interop<br/>official JS lib → typed arrays into WASM"]
```
Index of paths:
| Path | Effort | Zero-copy on reads | Notes | | Path | Effort | Zero-copy on reads | Notes |
|---|---|---|---| |---|---|---|---|
| A: FFI to FlatCC (C runtime) | Medium | ✅ | Bind generated C headers to Odin `foreign` | | A: FFI to FlatCC (C runtime) | Medium | ✅ | Bind generated C headers to Odin `foreign` |
@@ -190,14 +175,12 @@ typed Odin slices.
The rendering win only materializes if data stays zero-copy **into the frame The rendering win only materializes if data stays zero-copy **into the frame
loop**: loop**:
```mermaid 1. Fetch frame bytes → owned `[dynamic]u8` (or a slice pinned for the lifetime
flowchart LR of the frame).
A["fetch frame bytes → [dynamic]u8<br/>or a slice pinned for the frame"] 2. `verify` the buffer once.
B["verify the buffer once"] 3. Get `ra_slice := ObjectBatch.ra(&buf)``[]f64` view.
C["ObjectBatch.ra(&buf) → []f64 view"] 4. Per object in `update()`/`draw()`: read `ra[i]`, `dec[i]`, `z[i]` straight
D["per object in update()/draw()<br/>ra[i] · dec[i] · z[i] → rl.Vector3DrawPoint3D"] from that slice; build `rl.Vector3`; `DrawPoint3D`.
A --> B --> C --> D
```
No per-object allocation. The current `Galaxy { position, color }` dynamic array No per-object allocation. The current `Galaxy { position, color }` dynamic array
in `main.odin` is the data structure you'd replace with *slices into the in `main.odin` is the data structure you'd replace with *slices into the
@@ -24,17 +24,14 @@ offers two built-in mechanisms plus the community pattern:
### Option 1: Size-prefixed FlatBuffers (built-in) ### Option 1: Size-prefixed FlatBuffers (built-in)
```mermaid
flowchart LR
A["u32 LE<br/>total buffer len<br/><i>size prefix</i>"]
B["u32 LE<br/>root table offset"]
C["file identifier<br/>(4 bytes)"]
D["tables · vtables · data"]
A --> B --> C --> D
```
```rust ```rust
builder.finish_size_prefixed(root, Some("DESI")); builder.finish_size_prefixed(root, Some("DESI"));
// +---------------------------+
// | u32 LE: total buffer len | <-- size prefix
// | u32 LE: root table offset |
// | file identifier (4 bytes) |
// | ... data ... |
// +---------------------------+
``` ```
Reader side: Reader side:
@@ -56,12 +53,8 @@ message kinds).
### Option 2: Custom length-prefix framing (like `flatstream`) ### Option 2: Custom length-prefix framing (like `flatstream`)
```mermaid ```
flowchart LR [ u32 LE: message_len ] [ optional checksum (e.g. u32 crc/xxhash) ] [ flatbuffer payload ]
A["u32 LE<br/>message_len"]
B["optional checksum<br/>(u32 crc / xxhash)"]
C["FlatBuffer payload"]
A --> B --> C
``` ```
- `flatstream-rs` (see `03-rust-integration.md`) is a reference implementation - `flatstream-rs` (see `03-rust-integration.md`) is a reference implementation
@@ -267,16 +260,6 @@ FlatBuffers long-term (mmap-friendly, page-in-what-you-touch).
## Decision summary for this repo ## Decision summary for this repo
```mermaid
flowchart TD
A["HTTP GET → one FlatBuffer body per batch<br/>validate Rust builder + Odin reader"]
B["WebSocket → one Binary message per batch<br/>interactive path · no custom framing"]
C["Self-identifying messages<br/>file_identifier &#34;DESI&#34;"]
D["size-prefixed / flatstream-style framing<br/>or HTTP-range + mmap for static catalogs"]
A --> B --> C
C -. "later, if needed" .-> D
```
1. Start with **HTTP GET → one FlatBuffer body per batch** to validate the Rust 1. Start with **HTTP GET → one FlatBuffer body per batch** to validate the Rust
builder + Odin reader (no protocol work at all). builder + Odin reader (no protocol work at all).
2. Then add **WebSocket** with one `Binary` message per batch (no custom framing) 2. Then add **WebSocket** with one `Binary` message per batch (no custom framing)
@@ -108,15 +108,6 @@ message shape — see `10-performance-benchmarks.md`.)
## Bottom line ## Bottom line
```mermaid
flowchart TD
Q1{"zero-copy reads<br/>in the per-frame render loop?"}
Q1 -- "no" --> PB["Protobuf / gRPC<br/>decode once into draw buffers"]
Q1 -- "yes" --> Q2{"truly columnar?<br/>millions of rows"}
Q2 -- "yes" --> ARR["Apache Arrow IPC<br/>via OdinArrow"]
Q2 -- "no · batched vectors" --> FB["FlatBuffers · this proposal"]
```
- **FlatBuffers is the best default** for this project: the zero-copy read model - **FlatBuffers is the best default** for this project: the zero-copy read model
matches the render loop, the wire format is compact for numeric vectors, schema matches the render loop, the wire format is compact for numeric vectors, schema
evolution fits DESI's release cadence, and the Rust + WASM/JS official story evolution fits DESI's release cadence, and the Rust + WASM/JS official story
@@ -139,23 +139,7 @@ side / flatc* — see the "cross-language fixture" section below.
### 4. Cross-language conformance suite (THE key integration test) ### 4. Cross-language conformance suite (THE key integration test)
This is the test that actually catches incompatibility. Pipeline: This is the test that actually catches incompatibility. Design:
```mermaid
flowchart LR
S["schema/catalog.fbs"]
J["testdata/catalog_sample.json"]
S --> F["flatc --binary"]
J --> F
F --> BIN["committed .bin fixtures<br/>repo-checked-in"]
BIN --> OT["Odin tests<br/>assert identical values"]
BIN --> RT["Rust tests<br/>assert expected values"]
RT -. "deterministic builder" .-> PAR["byte-for-byte parity"]
OT -. "reads it" .-> PAR
PAR -. "catch drift" .-> F
```
Design:
1. **Static fixtures, committed to the repo** (`testdata/*.bin`): 1. **Static fixtures, committed to the repo** (`testdata/*.bin`):
- Built once by `flatc --binary <schema>.fbs <data>.json` (deterministic, - Built once by `flatc --binary <schema>.fbs <data>.json` (deterministic,
@@ -70,16 +70,6 @@ directly from the buffer each frame without allocations.
## Immediate Next Steps (when you're ready to implement) ## Immediate Next Steps (when you're ready to implement)
```mermaid
flowchart TD
A["Prototype schema<br/>catalog.fbs: Catalog · CatalogObject · ServerMessage union"]
B["Generate Rust code<br/>flatc --rust → api/build.rs · serve WS via axum"]
C["Prototype the Odin reader<br/>flatcc FFI · pure-Odin · OdinArrow"]
D["Static fixture files<br/>flatc --binary → committed .bin"]
E["Cross-language tests<br/>Rust + Odin read the same fixtures identically"]
A --> B --> C --> D --> E
```
1. **Prototype schema first.** Write `catalog.fbs` covering `Catalog`, `CatalogObject`, 1. **Prototype schema first.** Write `catalog.fbs` covering `Catalog`, `CatalogObject`,
and a `ServerMessage` union (handshake / catalog list / chunk of objects / end). and a `ServerMessage` union (handshake / catalog list / chunk of objects / end).
2. **Generate Rust code** via `flatc --rust` in an `api/build.rs` (see 2. **Generate Rust code** via `flatc --rust` in an `api/build.rs` (see
-8
View File
@@ -1,8 +0,0 @@
# Example API environment — passed via API_ENV_FILE (see root Makefile).
#
# The API also reads API_DESI_DATA from the environment (set by the root
# Makefile to resources/dev/desi_subset.json by default), so it is not
# repeated here. Values here win unless the same key is already set in the
# real process environment.
API_BIND_ADDR=127.0.0.1:8080
-58
View File
@@ -1,58 +0,0 @@
{
"catalogs": [
{
"name": "edr",
"release": "EDR",
"description": "DESI Early Data Release (local dev subset)",
"object_count": 3
},
{
"name": "dr1",
"release": "DR1",
"description": "DESI Data Release 1 (local dev subset)",
"object_count": 2
}
],
"objects": [
{
"id": "DESI_EDR_000000001",
"catalog": "edr",
"object_type": "GALAXY",
"ra": 150.123456,
"dec": 2.345678,
"redshift": 0.5521
},
{
"id": "DESI_EDR_000000002",
"catalog": "edr",
"object_type": "GALAXY",
"ra": 254.987654,
"dec": -15.203041,
"redshift": 1.1045
},
{
"id": "DESI_EDR_000000003",
"catalog": "edr",
"object_type": "QSO",
"ra": 75.001234,
"dec": 38.765432,
"redshift": 2.8756
},
{
"id": "DESI_DR1_000000001",
"catalog": "dr1",
"object_type": "STAR",
"ra": 188.556677,
"dec": 47.112233,
"redshift": 0.0001
},
{
"id": "DESI_DR1_000000002",
"catalog": "dr1",
"object_type": "GALAXY",
"ra": 300.445566,
"dec": 12.778899,
"redshift": 0.7742
}
]
}
-7
View File
@@ -1,7 +0,0 @@
# Example GUI environment — passed via GUI_ENV_FILE (see root Makefile).
#
# Point the renderer at the local dev API: `make run` also starts the API
# server, so http://127.0.0.1:8080 is the default. Values here win unless
# the same key is already set in the real process environment.
API_URL=http://127.0.0.1:8080
+6 -15
View File
@@ -18,19 +18,10 @@ case "$ARCH" in
;; ;;
esac esac
# If a prior job restored the install from cache, reuse it instead of curl -fL -o /tmp/odin.tar.gz \
# re-downloading. The extracted Odin binary already on PATH is authoritative; "https://github.com/odin-lang/Odin/releases/download/${VERSION}/odin-linux-${OBJ_ARCH}-${VERSION}.tar.gz"
# otherwise fetch the version (plus the raylib workaround) fresh. mkdir -p /tmp/odin
if [ -x /tmp/odin/odin ]; then tar -xzf /tmp/odin.tar.gz -C /tmp/odin --strip-components=1
echo "Odin ${VERSION} found in cache (host arch: ${ARCH}, release arch: ${OBJ_ARCH})"
BIN_DIR=/tmp/odin
else
curl -fL -o /tmp/odin.tar.gz \
"https://github.com/odin-lang/Odin/releases/download/${VERSION}/odin-linux-${OBJ_ARCH}-${VERSION}.tar.gz"
mkdir -p /tmp/odin
tar -xzf /tmp/odin.tar.gz -C /tmp/odin --strip-components=1
BIN_DIR=/tmp/odin
fi
# Work around an Odin binding bug: for ODIN_ARCH == .arm64 the vendored # Work around an Odin binding bug: for ODIN_ARCH == .arm64 the vendored
# raylib references `vendor/raylib/linux-arm/libraylib.a`, but the release # raylib references `vendor/raylib/linux-arm/libraylib.a`, but the release
@@ -42,7 +33,7 @@ if [ "${OBJ_ARCH}" = "arm64" ] \
fi fi
if [ -n "${GITHUB_PATH:-}" ]; then if [ -n "${GITHUB_PATH:-}" ]; then
echo "${BIN_DIR}" >> "$GITHUB_PATH" echo "/tmp/odin" >> "$GITHUB_PATH"
fi fi
echo "Odin ${VERSION} ready (host arch: ${ARCH}, release arch: ${OBJ_ARCH})" echo "Installed Odin ${VERSION} (host arch: ${ARCH}, release arch: ${OBJ_ARCH})"