← Commit history

open-source: publish full source under adom org

John Lauer ·9db0701858 ·3mo ago ·parent 08d4fdb
8 files changed +271
src/cli/hide.rsadded+22
@@ -0,0 +1,22 @@+use crate::cli::{eval, ok, DEFAULT_PORT};+use anyhow::Result;+use clap::Parser;++#[derive(Parser)]+pub struct Args {+    pub name: String,+    #[arg(long, default_value_t = 5)]+    pub timeout_secs: u64,+    #[arg(long, default_value_t = DEFAULT_PORT)]+    pub port: u16,+}++pub fn run(args: Args) -> Result<()> {+    let code = format!(+        "return window.adomStep ? window.adomStep.hide({}) : {{ error: 'adomStep not loaded' }};",+        serde_json::to_string(&args.name).unwrap()+    );+    eval::eval_snippet(&code, args.timeout_secs, args.port, true)?;+    ok(format!("hide {}", args.name));+    Ok(())+}
src/cli/mod.rsadded+45
@@ -0,0 +1,45 @@+pub mod console;+pub mod eval;+pub mod frame;+pub mod health;+pub mod hide;+pub mod install;+pub mod isolate;+pub mod list_components;+pub mod measure;+pub mod open;+pub mod pins;+pub mod screenshot;+pub mod show;+pub mod show_all;+pub mod status;+pub mod stop;+pub mod view;+pub mod view_cube;+pub mod view_library;++use colored::Colorize;++pub fn ok(msg: impl AsRef<str>) {+    println!("{} {}", "OK:".green().bold(), msg.as_ref());+}++pub fn err(msg: impl AsRef<str>) {+    eprintln!("{} {}", "ERROR:".red().bold(), msg.as_ref());+}++pub fn hint(msg: impl AsRef<str>) {+    eprintln!("{} {}", "Hint:".yellow().bold(), msg.as_ref());+}++pub const DEFAULT_PORT: u16 = 8851;++/// Service-step2glb base URL. Override at runtime with `STEP2GLB_SERVICE_API`.+pub const DEFAULT_STEP2GLB_URL: &str = "https://step2glb-gmdoncpxdwx0.adom.cloud";++pub fn step2glb_service_url() -> String {+    std::env::var("STEP2GLB_SERVICE_API")+        .unwrap_or_else(|_| DEFAULT_STEP2GLB_URL.to_string())+        .trim_end_matches('/')+        .to_string()+}
src/cli/screenshot.rsadded+89
@@ -0,0 +1,89 @@+use crate::cli::{eval, err, ok, DEFAULT_PORT};+use anyhow::Result;+use clap::Parser;+use std::path::PathBuf;++#[derive(Parser)]+pub struct Args {+    /// Output PNG path. Defaults to /tmp/adom-step-shot.png.+    #[arg(long)]+    pub out: Option<PathBuf>,++    #[arg(long, default_value_t = 1600)]+    pub width: u32,++    #[arg(long, default_value_t = 1000)]+    pub height: u32,++    #[arg(long, default_value_t = 10)]+    pub timeout_secs: u64,++    #[arg(long, default_value_t = DEFAULT_PORT)]+    pub port: u16,+}++pub fn run(args: Args) -> Result<()> {+    let out = args+        .out+        .unwrap_or_else(|| PathBuf::from("/tmp/adom-step-shot.png"));++    let code = format!(+        "return window.adomStep ? await window.adomStep.screenshot({{width: {}, height: {}}}) : {{ error: 'adomStep not loaded' }};",+        args.width, args.height+    );+    let result = eval::eval_snippet(&code, args.timeout_secs, args.port, true)?;+    if let Some(e) = result.get("error").and_then(|v| v.as_str()) {+        err(format!("screenshot failed: {e}"));+        return Err(anyhow::anyhow!("{e}"));+    }+    let data_url = result.as_str().unwrap_or("");+    let prefix = "data:image/png;base64,";+    if !data_url.starts_with(prefix) {+        err(format!("unexpected screenshot payload: {data_url}"));+        return Err(anyhow::anyhow!("bad payload"));+    }+    let b64 = &data_url[prefix.len()..];+    let bytes = base64_decode(b64)?;+    std::fs::write(&out, &bytes)?;+    ok(format!(+        "wrote {} ({} KB)",+        out.display(),+        bytes.len() / 1024+    ));+    Ok(())+}++/// Lean base64 decoder so we don't pull in the `base64` crate for one use.+fn base64_decode(s: &str) -> Result<Vec<u8>> {+    let s: String = s.chars().filter(|c| !c.is_whitespace()).collect();+    let map = |c: char| -> Result<u32> {+        Ok(match c {+            'A'..='Z' => c as u32 - 'A' as u32,+            'a'..='z' => c as u32 - 'a' as u32 + 26,+            '0'..='9' => c as u32 - '0' as u32 + 52,+            '+' => 62,+            '/' => 63,+            '=' => 0,+            _ => return Err(anyhow::anyhow!("bad base64 char: {c}")),+        })+    };+    let bytes = s.as_bytes();+    let mut out = Vec::with_capacity(bytes.len() * 3 / 4);+    let mut i = 0;+    while i + 3 < bytes.len() {+        let a = map(bytes[i] as char)?;+        let b = map(bytes[i + 1] as char)?;+        let c = map(bytes[i + 2] as char)?;+        let d = map(bytes[i + 3] as char)?;+        let v = (a << 18) | (b << 12) | (c << 6) | d;+        out.push((v >> 16) as u8);+        if bytes[i + 2] != b'=' {+            out.push(((v >> 8) & 0xFF) as u8);+        }+        if bytes[i + 3] != b'=' {+            out.push((v & 0xFF) as u8);+        }+        i += 4;+    }+    Ok(out)+}
src/cli/show.rsadded+22
@@ -0,0 +1,22 @@+use crate::cli::{eval, ok, DEFAULT_PORT};+use anyhow::Result;+use clap::Parser;++#[derive(Parser)]+pub struct Args {+    pub name: String,+    #[arg(long, default_value_t = 5)]+    pub timeout_secs: u64,+    #[arg(long, default_value_t = DEFAULT_PORT)]+    pub port: u16,+}++pub fn run(args: Args) -> Result<()> {+    let code = format!(+        "return window.adomStep ? window.adomStep.show({}) : {{ error: 'adomStep not loaded' }};",+        serde_json::to_string(&args.name).unwrap()+    );+    eval::eval_snippet(&code, args.timeout_secs, args.port, true)?;+    ok(format!("show {}", args.name));+    Ok(())+}
src/cli/show_all.rsadded+18
@@ -0,0 +1,18 @@+use crate::cli::{eval, ok, DEFAULT_PORT};+use anyhow::Result;+use clap::Parser;++#[derive(Parser)]+pub struct Args {+    #[arg(long, default_value_t = 5)]+    pub timeout_secs: u64,+    #[arg(long, default_value_t = DEFAULT_PORT)]+    pub port: u16,+}++pub fn run(args: Args) -> Result<()> {+    let code = "return window.adomStep ? window.adomStep.showAll() : { error: 'adomStep not loaded' };";+    eval::eval_snippet(code, args.timeout_secs, args.port, true)?;+    ok("showed all components");+    Ok(())+}
src/cli/status.rsadded+25
@@ -0,0 +1,25 @@+use crate::cli::{err, ok, DEFAULT_PORT};+use anyhow::Result;+use clap::Parser;+use std::time::Duration;++#[derive(Parser)]+pub struct Args {+    #[arg(long, default_value_t = DEFAULT_PORT)]+    pub port: u16,+}++pub fn run(args: Args) -> Result<()> {+    let url = format!("http://127.0.0.1:{}/state", args.port);+    match ureq::get(&url).timeout(Duration::from_secs(2)).call() {+        Ok(r) => {+            let body: serde_json::Value = r.into_json()?;+            ok(serde_json::to_string_pretty(&body).unwrap_or_default());+            Ok(())+        }+        Err(e) => {+            err(format!("not running on port {}: {e}", args.port));+            Err(anyhow::anyhow!("not running"))+        }+    }+}
src/cli/stop.rsadded+19
@@ -0,0 +1,19 @@+use crate::cli::{ok, DEFAULT_PORT};+use anyhow::Result;+use clap::Parser;+use std::time::Duration;++#[derive(Parser)]+pub struct Args {+    #[arg(long, default_value_t = DEFAULT_PORT)]+    pub port: u16,+}++pub fn run(args: Args) -> Result<()> {+    let url = format!("http://127.0.0.1:{}/shutdown", args.port);+    let _ = ureq::post(&url)+        .timeout(Duration::from_secs(2))+        .send_string("");+    ok(format!("sent shutdown to port {}", args.port));+    Ok(())+}
src/cli/view_cube.rsadded+31
@@ -0,0 +1,31 @@+use crate::cli::{eval, err, ok, DEFAULT_PORT};+use anyhow::Result;+use clap::Parser;++#[derive(Parser)]+pub struct Args {+    /// View name: front | back | left | right | top | bottom | iso | home.+    pub view: String,+    #[arg(long, default_value_t = 5)]+    pub timeout_secs: u64,+    #[arg(long, default_value_t = DEFAULT_PORT)]+    pub port: u16,+}++pub fn run(args: Args) -> Result<()> {+    let v = args.view.trim().to_lowercase();+    if !matches!(+        v.as_str(),+        "front" | "back" | "left" | "right" | "top" | "bottom" | "iso" | "home"+    ) {+        err(format!("unknown view '{v}'"));+        return Err(anyhow::anyhow!("bad view"));+    }+    let code = format!(+        "return window.adomStep ? window.adomStep.setView({}) : {{ error: 'adomStep not loaded' }};",+        serde_json::to_string(&v).unwrap()+    );+    eval::eval_snippet(&code, args.timeout_secs, args.port, true)?;+    ok(format!("set view: {v}"));+    Ok(())+}