standard-plugin-cli 0.1.1

standard-plugin: scaffold, build, check and pack Standard Code plugins
Documentation
//! The worlds of `standard:plugin@2.0.0`, read from the SDK's WIT package,
//! as sets of import and export names a component can be checked
//! against.

use std::collections::BTreeSet;

use standard_plugin_manifest::World;
use wit_parser::{Resolve, WorldItem, WorldKey};

/// The WIT package this CLI checks against: the one the SDK version it
/// pins builds its bindings from.
pub const WIT: &str = standard_plugin::sources::WIT;

/// What a world lets a component import and requires it to export.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct WorldShape {
    /// Interface names (`standard:plugin/values@2.0.0`) and the types the
    /// world `use`s (`event`, `power`).
    pub imports: BTreeSet<String>,
    /// Exported function names (`activate`, `frame`, ...) and interface
    /// names (`standard:plugin/companion@2.0.0`).
    pub exports: BTreeSet<String>,
}

/// The shape of `world`, with the implicit imports its `use`s pull in.
pub fn shape(world: World) -> WorldShape {
    let mut resolve = Resolve::default();
    let package = resolve
        .push_source("plugin.wit", WIT)
        .expect("the embedded WIT package parses");
    let id = resolve
        .select_world(&[package], Some(world.wit_name()))
        .expect("the embedded WIT package has both worlds");
    let world = &resolve.worlds[id];
    let name = |key: &WorldKey, item: &WorldItem| match (key, item) {
        (_, WorldItem::Interface { id, .. }) => resolve.id_of(*id),
        (WorldKey::Name(name), WorldItem::Function(_) | WorldItem::Type { .. }) => {
            Some(name.clone())
        }
        _ => None,
    };
    WorldShape {
        imports: world
            .imports
            .iter()
            .filter_map(|(key, item)| name(key, item))
            .collect(),
        exports: world
            .exports
            .iter()
            .filter_map(|(key, item)| name(key, item))
            .collect(),
    }
}

/// `standard:plugin/<name>@2.0.0` for an interface of this package.
pub fn interface(name: &str) -> String {
    format!("standard:plugin/{name}@2.0.0")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_worlds_list_their_interfaces_and_exports() {
        let ui = shape(World::Ui);
        assert!(ui.imports.contains(&interface("surface")));
        assert!(ui.imports.contains(&interface("claims")));
        assert!(!ui.imports.contains(&interface("daemon-process")));
        assert_eq!(
            ui.exports,
            ["activate", "deactivate", "event", "frame"]
                .map(String::from)
                .into()
        );
        let daemon = shape(World::Daemon);
        assert!(daemon.imports.contains(&interface("daemon-panes")));
        assert!(daemon.exports.contains("drive"));
        assert!(daemon.exports.contains(&interface("companion")));
        assert!(!daemon.exports.contains("handle-call"));
    }
}