mecha10_cli_core/
paths.rs1#![allow(dead_code)]
6
7pub const PROJECT_CONFIG: &str = "mecha10.json";
9
10pub mod project {
12 pub const NODES_DIR: &str = "nodes";
14
15 pub const NODES_MECHA10_DIR: &str = "nodes/@mecha10";
18
19 pub fn framework_node_schema_dir(node_name: &str) -> String {
22 format!("{}/{}", NODES_MECHA10_DIR, node_name)
23 }
24
25 pub const DRIVERS_DIR: &str = "drivers";
27
28 pub const TYPES_DIR: &str = "types";
30
31 pub const BEHAVIORS_DIR: &str = "configs/behaviors";
35
36 pub const MODELS_DIR: &str = "models";
38
39 pub const MODELS_CATALOG_DIR: &str = "models/catalog";
42
43 pub const LOGS_DIR: &str = "logs";
45
46 pub const SIMULATION_DIR: &str = "configs/simulation";
50
51 pub const SIMULATION_MODELS_DIR: &str = "configs/simulation/models";
53
54 pub const SIMULATION_ENVIRONMENTS_DIR: &str = "configs/simulation/environments";
56
57 pub const SIMULATION_IMAGES_DIR: &str = "configs/simulation/images";
60
61 pub const RL_TASKS_DIR: &str = "configs/rl/tasks";
65
66 pub const SIMULATION_OUTPUT_DIR: &str = "configs/simulation/output";
68
69 pub const DATASET_JOBS_DIR: &str = "configs/datasets/jobs";
78
79 pub const TARGET_DIR: &str = "target";
81
82 pub const SRC_DIR: &str = "src";
84
85 pub const STATE_DIR: &str = ".mecha10";
88
89 pub const DEV_PREFLIGHT_CACHE_FILE: &str = ".mecha10/dev-preflight-cache.json";
91
92 pub const ASSETS_CACHE_DIR: &str = ".mecha10/assets";
95}
96
97pub mod config {
99 use super::project;
100
101 pub const DIR: &str = "configs";
103
104 pub const NODES_DIR: &str = "configs/nodes";
106
107 pub const NODES_MECHA10_DIR: &str = "configs/nodes/@mecha10";
109
110 fn framework_config(category_dir: &str, name: &str) -> String {
112 format!("{category_dir}/@mecha10/{name}/config.json")
113 }
114
115 fn project_config(category_dir: &str, name: &str) -> String {
117 format!("{category_dir}/{name}/config.json")
118 }
119
120 fn config_candidates(category_dir: &str, name: &str) -> [String; 2] {
123 [framework_config(category_dir, name), project_config(category_dir, name)]
124 }
125
126 pub fn framework_node(node_name: &str) -> String {
128 framework_config(NODES_DIR, node_name)
129 }
130
131 pub fn project_node(node_name: &str) -> String {
133 project_config(NODES_DIR, node_name)
134 }
135
136 pub fn framework_node_dir(node_name: &str) -> String {
140 format!("{}/{}", NODES_MECHA10_DIR, node_name)
141 }
142
143 pub fn node_config_candidates(node_name: &str) -> [String; 2] {
146 config_candidates(NODES_DIR, node_name)
147 }
148
149 pub fn framework_behavior(behavior_name: &str) -> String {
152 framework_config(project::BEHAVIORS_DIR, behavior_name)
153 }
154
155 pub fn framework_behavior_dir(behavior_name: &str) -> String {
158 format!("{}/@mecha10/{}", project::BEHAVIORS_DIR, behavior_name)
159 }
160
161 pub fn project_behavior(behavior_name: &str) -> String {
164 project_config(project::BEHAVIORS_DIR, behavior_name)
165 }
166
167 pub fn behavior_config_candidates(behavior_name: &str) -> [String; 2] {
170 [project_behavior(behavior_name), framework_behavior(behavior_name)]
171 }
172}
173
174pub mod docker {
176 pub const DIR: &str = "docker";
178
179 pub const COMPOSE_FILE: &str = "docker/docker-compose.yml";
181
182 pub const COMPOSE_REMOTE_FILE: &str = "docker/docker-compose.remote.yml";
184
185 pub const DOCKERFILE_REMOTE: &str = "docker/Dockerfile.remote";
187
188 pub const ENTRYPOINT_REMOTE: &str = "docker/entrypoint.sh";
190
191 pub const COMPOSE_RL_FILE: &str = "docker/docker-compose.rl.yml";
193
194 pub const DOCKERFILE_RL: &str = "docker/Dockerfile.rl";
196
197 pub const ENTRYPOINT_RL: &str = "docker/entrypoint-rl.sh";
201}
202
203pub mod env {
205 pub const EXAMPLE: &str = ".env.example";
207
208 pub const FILE: &str = ".env";
210}
211
212pub mod meta {
214 pub const README: &str = "README.md";
216
217 pub const GITIGNORE: &str = ".gitignore";
219
220 pub const PACKAGE_JSON: &str = "package.json";
222
223 pub const REQUIREMENTS_TXT: &str = "requirements.txt";
225
226 pub const PYTHON_VERSION: &str = ".python-version";
229}
230
231pub mod rust {
233 pub const CARGO_TOML: &str = "Cargo.toml";
235
236 pub const CARGO_LOCK: &str = "Cargo.lock";
238
239 pub const MAIN_RS: &str = "src/main.rs";
241
242 pub const LIB_RS: &str = "src/lib.rs";
244
245 pub const BUILD_RS: &str = "build.rs";
247
248 pub const RUSTFMT_TOML: &str = "rustfmt.toml";
250}
251
252pub mod model {
254 pub const ONNX_FILE: &str = "model.onnx";
256
257 pub const LABELS_FILE: &str = "labels.txt";
259
260 pub const CONFIG_FILE: &str = "config.json";
262}
263
264pub mod user {
266 use std::path::PathBuf;
267
268 pub const MECHA10_DIR: &str = mecha10_core::fs_utils::MECHA10_DIR;
272
273 pub const CREDENTIALS_FILE: &str = ".mecha10/credentials.json";
277
278 pub const TEMPLATES_DIR: &str = ".mecha10/templates";
280
281 pub const BIN_DIR: &str = ".mecha10/bin";
283
284 pub fn mecha10_dir() -> PathBuf {
286 dirs::home_dir().unwrap_or_else(|| PathBuf::from(".")).join(MECHA10_DIR)
287 }
288
289 pub fn credentials_file() -> PathBuf {
293 mecha10_auth::default_credentials_path()
294 }
295
296 pub fn templates_dir() -> PathBuf {
298 mecha10_dir().join("templates")
299 }
300
301 pub fn bin_dir() -> PathBuf {
303 mecha10_dir().join("bin")
304 }
305
306 pub fn bin(name: &str) -> PathBuf {
308 bin_dir().join(name)
309 }
310
311 pub fn cargo_bin(name: &str) -> PathBuf {
313 home_bin_dir(".cargo", name)
314 }
315
316 pub fn local_bin(name: &str) -> PathBuf {
318 home_bin_dir(".local", name)
319 }
320
321 fn home_bin_dir(home_subdir: &str, name: &str) -> PathBuf {
324 mecha10_core::fs_utils::home_subpath(&[home_subdir, "bin", name])
325 }
326}
327
328pub mod container {
330 pub const PROJECT_ROOT: &str = "/project";
332
333 pub const FRAMEWORK_ROOT: &str = "/mecha10";
335
336 pub const APP_ROOT: &str = "/app";
338
339 pub const REMOTE_CONFIG: &str = "/app/mecha10.json";
341
342 pub fn project_path(relative: &str) -> String {
344 format!("{}/{}", PROJECT_ROOT, relative)
345 }
346
347 pub fn framework_path(relative: &str) -> String {
349 format!("{}/{}", FRAMEWORK_ROOT, relative)
350 }
351}
352
353pub mod framework {
355 pub const PACKAGES_DIR: &str = "packages";
357
358 pub const NODES_DIR: &str = "packages/nodes";
360
361 pub const DRIVERS_DIR: &str = "packages/drivers";
363
364 pub const ROOT_MARKER_DIR: &str = "packages/core";
366
367 pub const RELEASE_BINARY: &str = "target/release/mecha10";
369
370 pub const DEBUG_BINARY: &str = "target/debug/mecha10";
372
373 pub fn node_config(node_name: &str) -> String {
375 format!("packages/nodes/{}/configs/config.json", node_name)
376 }
377}
378
379pub mod urls {
381 pub const MECHA10_API_URL: &str = "https://mecha.industries/api";
384
385 pub fn mecha10_api_base_url() -> String {
389 std::env::var("MECHA10_API_URL")
390 .unwrap_or_else(|_| MECHA10_API_URL.to_string())
391 .trim_end_matches('/')
392 .to_string()
393 }
394
395 pub fn mecha10_api_path(path: &str) -> String {
398 format!("{}/{}", mecha10_api_base_url(), path.trim_start_matches('/'))
399 }
400
401 pub const AUTH_URL: &str = "https://mecha.industries/api/auth";
403}
404
405pub fn target_path(profile: &str, binary: &str) -> String {
407 format!("target/{}/{}", profile, binary)
408}
409
410pub fn target_path_with_triple(target: Option<&str>, profile: &str) -> String {
412 match target {
413 Some(t) => format!("target/{}/{}", t, profile),
414 None => format!("target/{}", profile),
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
423 fn credentials_file_matches_mecha10_auth_source_of_truth() {
424 assert_eq!(user::credentials_file(), mecha10_auth::default_credentials_path());
425 }
426
427 #[test]
430 fn cargo_bin_and_local_bin_join_name_under_their_own_home_subdir() {
431 let home = dirs::home_dir().expect("home dir must resolve in test environment");
432 assert_eq!(
433 user::cargo_bin("mecha10"),
434 home.join(".cargo").join("bin").join("mecha10")
435 );
436 assert_eq!(
437 user::local_bin("mecha10"),
438 home.join(".local").join("bin").join("mecha10")
439 );
440 }
441
442 #[test]
443 fn simulation_project_dirs_share_the_simulation_root() {
444 assert_eq!(project::SIMULATION_MODELS_DIR, "configs/simulation/models");
445 assert_eq!(project::SIMULATION_ENVIRONMENTS_DIR, "configs/simulation/environments");
446 for dir in [project::SIMULATION_MODELS_DIR, project::SIMULATION_ENVIRONMENTS_DIR] {
447 assert!(dir.starts_with(&format!("{}/", project::SIMULATION_DIR)));
448 }
449 }
450
451 #[test]
454 fn rl_tasks_dir_lives_under_configs_rl_not_simulation() {
455 assert_eq!(project::RL_TASKS_DIR, "configs/rl/tasks");
456 assert!(!project::RL_TASKS_DIR.starts_with(&format!("{}/", project::SIMULATION_DIR)));
457 }
458
459 #[test]
463 fn dataset_jobs_dir_does_not_live_under_the_rl_train_job_catalog() {
464 assert_eq!(project::DATASET_JOBS_DIR, "configs/datasets/jobs");
465 assert!(!project::DATASET_JOBS_DIR.starts_with("configs/rl/jobs"));
466 }
467
468 #[test]
469 fn test_config_paths() {
470 assert_eq!(
471 config::framework_node("object-detector"),
472 "configs/nodes/@mecha10/object-detector/config.json"
473 );
474 assert_eq!(config::project_node("camera"), "configs/nodes/camera/config.json");
475 }
476
477 #[test]
478 fn test_framework_node_dir() {
479 assert_eq!(config::framework_node_dir("speaker"), "configs/nodes/@mecha10/speaker");
480 }
481
482 #[test]
483 fn test_framework_node_schema_dir_lives_under_nodes_not_configs_nodes() {
484 assert_eq!(project::framework_node_schema_dir("speaker"), "nodes/@mecha10/speaker");
485 assert_ne!(
486 project::framework_node_schema_dir("speaker"),
487 config::framework_node_dir("speaker"),
488 "schema dir (wave 10) must not collide with the config.json dir"
489 );
490 }
491
492 #[test]
493 fn test_node_config_candidates_order() {
494 assert_eq!(
495 config::node_config_candidates("teleop"),
496 [
497 "configs/nodes/@mecha10/teleop/config.json".to_string(),
498 "configs/nodes/teleop/config.json".to_string(),
499 ]
500 );
501 }
502
503 #[test]
504 fn test_behavior_config_paths() {
505 assert_eq!(
506 config::framework_behavior("idle_wander"),
507 "configs/behaviors/@mecha10/idle_wander/config.json"
508 );
509 assert_eq!(
510 config::project_behavior("idle_wander"),
511 "configs/behaviors/idle_wander/config.json"
512 );
513 }
514
515 #[test]
516 fn test_framework_behavior_dir() {
517 assert_eq!(
518 config::framework_behavior_dir("idle_wander"),
519 "configs/behaviors/@mecha10/idle_wander"
520 );
521 assert_eq!(
522 config::framework_behavior("idle_wander"),
523 format!("{}/config.json", config::framework_behavior_dir("idle_wander"))
524 );
525 }
526
527 #[test]
528 fn test_behavior_config_candidates_order() {
529 assert_eq!(
530 config::behavior_config_candidates("idle_wander"),
531 [
532 "configs/behaviors/idle_wander/config.json".to_string(),
533 "configs/behaviors/@mecha10/idle_wander/config.json".to_string(),
534 ]
535 );
536 }
537
538 #[test]
539 fn test_target_path() {
540 assert_eq!(target_path("release", "my-node"), "target/release/my-node");
541 assert_eq!(
542 target_path_with_triple(Some("aarch64-unknown-linux-gnu"), "release"),
543 "target/aarch64-unknown-linux-gnu/release"
544 );
545 assert_eq!(target_path_with_triple(None, "debug"), "target/debug");
546 }
547
548 #[test]
549 fn test_container_paths() {
550 assert_eq!(
551 container::project_path("configs/test.json"),
552 "/project/configs/test.json"
553 );
554 assert_eq!(container::framework_path("packages/core"), "/mecha10/packages/core");
555 }
556
557 #[test]
558 fn test_mecha10_api_base_url_default() {
559 let _guard = ENV_MUTEX.lock().unwrap();
560 std::env::remove_var("MECHA10_API_URL");
561 assert_eq!(urls::mecha10_api_base_url(), "https://mecha.industries/api");
562 }
563
564 #[test]
565 fn test_mecha10_api_base_url_env_override() {
566 let _guard = ENV_MUTEX.lock().unwrap();
567 std::env::set_var("MECHA10_API_URL", "http://localhost:4000/api/");
568 assert_eq!(urls::mecha10_api_base_url(), "http://localhost:4000/api");
569 std::env::remove_var("MECHA10_API_URL");
570 }
571
572 #[test]
573 fn test_mecha10_api_path_does_not_double_the_api_segment() {
574 let _guard = ENV_MUTEX.lock().unwrap();
575 std::env::remove_var("MECHA10_API_URL");
576 assert_eq!(
577 urls::mecha10_api_path("/models/catalog"),
578 "https://mecha.industries/api/models/catalog"
579 );
580 }
581
582 #[test]
583 fn test_mecha10_api_path_works_without_a_leading_slash() {
584 let _guard = ENV_MUTEX.lock().unwrap();
585 std::env::remove_var("MECHA10_API_URL");
586 assert_eq!(
587 urls::mecha10_api_path("downloads/model/deepghs/yolos/yolov8n/model.onnx"),
588 "https://mecha.industries/api/downloads/model/deepghs/yolos/yolov8n/model.onnx"
589 );
590 }
591
592 static ENV_MUTEX: std::sync::Mutex<()> = std::sync::Mutex::new(());
595}