orb_unpack/
lib.rs

1use anyhow::{bail, Result};
2use std::{
3    fs::{self, create_dir_all},
4    path::{Path, PathBuf},
5};
6use yaml_rust::{yaml::Hash, Yaml, YamlEmitter, YamlLoader};
7
8/// Unpacks an orb YAML file from the source path into the destination directory
9pub fn unpack_from_file(source: PathBuf, dest: PathBuf) -> Result<()> {
10    let orb = get_orb_from_path(source)?;
11    let orb_root = unpack_to_dir(orb, &dest)?;
12    write_root_file(orb_root, dest)
13}
14
15/// Unpacks the relevant sections into separate files and directories
16/// Returns the remaining YAML after those sections are removed
17fn unpack_to_dir(mut orb: Hash, dest: &Path) -> Result<Hash> {
18    // Write sections to subdirectories
19    for section_name in ["commands", "jobs", "executors", "examples"] {
20        if let Some(Yaml::Hash(section)) = orb.remove(&Yaml::from_str(section_name)) {
21            extract_to_subdirectory(section, &dest.join(section_name))?;
22        }
23    }
24    Ok(orb)
25}
26
27fn extract_to_subdirectory(section: Hash, dest: &PathBuf) -> Result<()> {
28    // Ensure that target subdirectory exists
29    create_dir_all(&dest)?;
30
31    for (key, value) in section.iter() {
32        if let Yaml::String(key) = key {
33            let file_name = format!("{key}.yml");
34            let file_path = dest.join(file_name);
35            write_yaml_to_file(file_path, value)?;
36        } else {
37            bail!("all keys must be strings")
38        }
39    }
40    Ok(())
41}
42
43fn write_root_file(root: Hash, dest: PathBuf) -> Result<()> {
44    let root_path = dest.join("@orb.yml");
45    write_yaml_to_file(root_path, &Yaml::Hash(root))
46}
47
48fn write_yaml_to_file(path: PathBuf, value: &Yaml) -> Result<()> {
49    let mut output = String::new();
50    let mut emitter = YamlEmitter::new(&mut output);
51    emitter.dump(value)?;
52    fs::write(path, &output[4..])?; // Skip the first three characters because the emitter writes --- at the start
53    Ok(())
54}
55
56fn get_orb_from_path(path: PathBuf) -> Result<Hash> {
57    let file_contents = fs::read_to_string(path)?;
58    let yaml_documents = YamlLoader::load_from_str(&file_contents)?;
59
60    match &yaml_documents[..] {
61        [Yaml::Hash(orb)] => Ok(orb.clone()),
62        _ => bail!(
63            "expected 1 document in file, but found {}",
64            yaml_documents.len()
65        ),
66    }
67}