//! Honest capability report for the foundation shell. //! //! Later features add import, animation, rendering and preview. Until those //! features land, this module must keep reporting them as not built. /// Product capability named by the design. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Capability { ProjectShell, ObjMtlTextures, Fbx, GltfGlb, SkeletalAnimation, AttachmentEditor, PaperdollRender, Preview2d, } /// Whether this build actually contains the capability. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Availability { Present, NotBuilt, } /// What the process should do with the command line it received. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CliRequest { OpenWindow, PrintStatus, PrintHelp, } /// Rejected command line. A model path is rejected on purpose: accepting one /// would look like import support. #[derive(Debug, Clone, PartialEq, Eq)] pub enum CliError { UnknownArgument(String), } const CAPABILITIES: [Capability; 8] = [ Capability::ProjectShell, Capability::ObjMtlTextures, Capability::Fbx, Capability::GltfGlb, Capability::SkeletalAnimation, Capability::AttachmentEditor, Capability::PaperdollRender, Capability::Preview2d, ]; pub fn capabilities() -> &'static [Capability] { &CAPABILITIES } pub fn availability(capability: Capability) -> Availability { match capability { Capability::ProjectShell => Availability::Present, Capability::ObjMtlTextures | Capability::Fbx | Capability::GltfGlb | Capability::SkeletalAnimation | Capability::AttachmentEditor | Capability::PaperdollRender | Capability::Preview2d => Availability::NotBuilt, } } pub fn machine_key(capability: Capability) -> &'static str { match capability { Capability::ProjectShell => "project_shell", Capability::ObjMtlTextures => "obj_mtl_textures", Capability::Fbx => "fbx", Capability::GltfGlb => "gltf_glb", Capability::SkeletalAnimation => "skeletal_animation", Capability::AttachmentEditor => "attachment_editor", Capability::PaperdollRender => "paperdoll_render", Capability::Preview2d => "preview_2d", } } pub fn machine_state(availability: Availability) -> &'static str { match availability { Availability::Present => "present", Availability::NotBuilt => "not_built", } } pub fn dutch_label(capability: Capability) -> &'static str { match capability { Capability::ProjectShell => "projectschil", Capability::ObjMtlTextures => "OBJ + MTL + textures", Capability::Fbx => "FBX", Capability::GltfGlb => "glTF/GLB", Capability::SkeletalAnimation => "skeletanimatie", Capability::AttachmentEditor => "3D-attachmenteditor", Capability::PaperdollRender => "paperdoll-render", Capability::Preview2d => "2D-preview", } } pub fn dutch_state(availability: Availability) -> &'static str { match availability { Availability::Present => "aanwezig", Availability::NotBuilt => "niet gebouwd", } } pub fn dutch_summary() -> &'static str { "Deze build bewaart een .ppd-projectmap. OBJ+MTL+textures, FBX, glTF/GLB, skeletanimatie, de attachment-editor, paperdoll-render en de 2D-preview zijn niet gebouwd. Genoemde modelbestanden worden niet geladen." } /// Stable status text shared by `--status` and the window. pub fn report() -> String { let mut lines = Vec::with_capacity(2 + CAPABILITIES.len()); lines.push("name: Paperdoll Renderer".to_string()); lines.push("phase: project-files".to_string()); for capability in CAPABILITIES { lines.push(format!( "{}: {}", machine_key(capability), machine_state(availability(capability)) )); } lines.join("\n") } /// Shared libraries the X11 window loads. `ldd` does not list them: eframe /// opens them at runtime. A missing one panics inside that loader, so the /// shell checks first. pub const LINUX_WINDOW_LIBRARIES: &[&str] = &[ "libxkbcommon-x11.so.0", "libxkbcommon.so.0", "libxcb.so.1", "libX11.so.6", "libGL.so.1", ]; pub fn missing_library_message(missing: &[&str]) -> String { format!( "het statusvenster kan niet openen; deze bibliotheken ontbreken: {}. Import en render zijn niet gebouwd.", missing.join(", ") ) } #[cfg(target_os = "linux")] pub fn missing_window_libraries() -> Vec<&'static str> { LINUX_WINDOW_LIBRARIES .iter() .copied() .filter(|name| { // Safety: `name` is a nul-free soname literal. A missing library // returns Err; no symbol from the library is called. unsafe { libloading::Library::new(name).is_err() } }) .collect() } pub fn help_text() -> &'static str { "\ Paperdoll Renderer — projectbestanden Gebruik: paperdoll-renderer open het projectvenster paperdoll-renderer --status print de status en sluit paperdoll-renderer --help toon deze tekst Een modelpad op de opdrachtregel wordt niet geaccepteerd. Import en render zijn niet gebouwd." } pub fn parse_cli<'a, I>(args: I) -> Result where I: IntoIterator, { let mut request = CliRequest::OpenWindow; for arg in args { match arg { "--help" | "-h" => request = CliRequest::PrintHelp, "--status" => { if request != CliRequest::PrintHelp { request = CliRequest::PrintStatus; } } other => { return Err(CliError::UnknownArgument(other.to_string())); } } } Ok(request) } #[cfg(test)] mod tests { use super::*; #[test] fn only_the_project_shell_is_present() { assert_eq!( availability(Capability::ProjectShell), Availability::Present ); for capability in capabilities() { if *capability == Capability::ProjectShell { continue; } assert_eq!( availability(*capability), Availability::NotBuilt, "{capability:?} must stay not built in the foundation" ); } } #[test] fn status_report_names_every_format_as_not_built() { let report = report(); assert!(report.starts_with("name: Paperdoll Renderer\nphase: project-files\n")); for key in [ "obj_mtl_textures", "fbx", "gltf_glb", "skeletal_animation", "attachment_editor", "paperdoll_render", "preview_2d", ] { assert!( report.contains(&format!("{key}: not_built")), "missing {key} in {report}" ); assert!( !report.contains(&format!("{key}: present")), "{key} must not be reported as present" ); } assert!(report.contains("project_shell: present")); let summary = dutch_summary(); assert!(summary.contains("niet gebouwd")); assert!(summary.contains("niet geladen")); let lower = summary.to_lowercase(); assert!(!lower.contains("import werkt")); assert!(!lower.contains("render werkt")); } #[test] fn cli_opens_a_window_or_prints_status_and_rejects_model_paths() { assert_eq!(parse_cli(std::iter::empty()), Ok(CliRequest::OpenWindow)); assert_eq!(parse_cli(["--status"]), Ok(CliRequest::PrintStatus)); assert_eq!(parse_cli(["--help"]), Ok(CliRequest::PrintHelp)); assert_eq!(parse_cli(["-h", "--status"]), Ok(CliRequest::PrintHelp)); assert_eq!( parse_cli(["tests/fixtures/bodies/test-body-male.glb"]), Err(CliError::UnknownArgument( "tests/fixtures/bodies/test-body-male.glb".to_string() )) ); assert_eq!( parse_cli(["--status", "--render"]), Err(CliError::UnknownArgument("--render".to_string())) ); assert!(help_text().contains("niet gebouwd")); } #[test] fn missing_library_message_names_the_library_and_does_not_claim_import() { let message = missing_library_message(&["libxkbcommon-x11.so.0"]); assert!(message.contains("libxkbcommon-x11.so.0")); assert!(message.contains("niet gebouwd")); assert!(!message.to_lowercase().contains("import werkt")); assert!(!LINUX_WINDOW_LIBRARIES.is_empty()); } }