Skip to main content

yog_cli/content/import/
mod.rs

1//! `yog import` — turns models made elsewhere into `content/` files:
2//!
3//! - `yog import resourcepack <zip|dir> [--model <id>] [--all]` — a
4//!   resource pack's (or a mod's `assets/`) block and item models: parents
5//!   resolved (a vanilla template stays a template, other parents are
6//!   flattened from the pack or the Minecraft client jar), textures copied
7//!   into `resources/`, the blockstate's rotations kept;
8//! - `yog import bbmodel <file.bbmodel> [--id <name>] [--item]` — a
9//!   Blockbench project in the Java block/item format, textures included;
10//! - `yog import font <yog:font/name> [--id <path>] [--chars <ranges>]` — a copy of
11//!   one of the game's fonts as the mod's own `.yfont` ([`font`]).
12
13mod bbmodel;
14mod blocks;
15mod build;
16mod fields;
17mod font;
18mod model;
19mod pack;
20mod target;
21
22pub(crate) use font::copy_path as font_copy_path;
23
24use target::Target;
25
26const USAGE: &str = "usage:\n  yog import resourcepack <pack.zip|dir> [--model <ns:block/name|ns:item/name>] [--all] [--force]\n  yog import bbmodel <file.bbmodel> [--id <name>] [--item] [--force]\n  yog import font <yog:font/name> [--id <path>] [--chars <hex ranges>] [--force]";
27
28/// The flags `yog import` takes: those with a value after them, and the rest.
29const MODEL: &str = "--model";
30const ID: &str = "--id";
31const CHARS: &str = "--chars";
32const WITH_VALUE: [&str; 3] = [MODEL, ID, CHARS];
33const FORCE: &str = "--force";
34const ALL: &str = "--all";
35const ITEM: &str = "--item";
36/// What a flag starts with.
37const FLAG: &str = "--";
38/// What the subcommands import.
39const RESOURCEPACK: &str = "resourcepack";
40const BBMODEL: &str = "bbmodel";
41
42/// The words of `args` after the subcommand that aren't flags or a
43/// flag's value.
44fn positional(args: &[String]) -> Vec<&str> {
45    let mut out = Vec::new();
46    let mut skip = false;
47    for a in args.iter().skip(1) {
48        if skip {
49            skip = false;
50        } else if WITH_VALUE.contains(&a.as_str()) {
51            skip = true;
52        } else if !a.starts_with(FLAG) {
53            out.push(a.as_str());
54        }
55    }
56    out
57}
58
59/// What an import wrote, and what to warn of.
60pub struct Imported {
61    /// The files it wrote, under the mod's root.
62    pub written: Vec<String>,
63    /// What it left out or changed, to tell.
64    pub warnings: Vec<String>,
65}
66
67/// `yog import ...` (args after `import`); an error when nothing was written.
68pub fn import_command(args: &[String]) -> Result<Imported, String> {
69    let flag = |name: &str| args.iter().any(|a| a == name);
70    let value = |name: &str| {
71        args.iter()
72            .position(|a| a == name)
73            .and_then(|i| args.get(i + 1))
74            .map(String::as_str)
75    };
76    let positional = positional(args);
77    let root = std::env::current_dir().map_err(|e| e.to_string())?;
78    let mod_id = read_mod_id(&root)?;
79    let mut target = Target {
80        root: root.clone(),
81        ns: super::valid_namespace(&mod_id).then_some(mod_id),
82        force: flag(FORCE),
83        written: Vec::new(),
84        warnings: Vec::new(),
85    };
86    match (args.first().map(String::as_str), positional.first()) {
87        (Some(RESOURCEPACK), Some(src)) => {
88            let pack = pack::Pack::open(&root.join(src))?;
89            blocks::import_pack(&mut target, &pack, value(MODEL), flag(ALL))?;
90        }
91        (Some(BBMODEL), Some(file)) => {
92            bbmodel::import_bbmodel(&mut target, &root.join(file), value(ID), flag(ITEM))?
93        }
94        (Some(kind), Some(alias)) if kind == yog_ids::kind::FONT => {
95            font::import_font(&mut target, alias, value(ID), value(CHARS))?
96        }
97        _ => return Err(USAGE.into()),
98    }
99    if target.written.is_empty() {
100        return Err(format!(
101            "nothing imported{}",
102            target
103                .warnings
104                .iter()
105                .map(|w| format!("\n  {w}"))
106                .collect::<String>()
107        ));
108    }
109    Ok(Imported {
110        written: target.written,
111        warnings: target.warnings,
112    })
113}
114
115/// The mod id `yog.toml` in `root` gives.
116fn read_mod_id(root: &std::path::Path) -> Result<String, String> {
117    let path = root.join(yog_ids::file::MANIFEST);
118    if !path.is_file() {
119        return Err("no yog.toml in the current directory — run `yog import` in a mod".into());
120    }
121    crate::manifest::YogToml::read(&path).map(|m| m.id)
122}
123
124#[cfg(test)]
125mod tests;