Skip to main content

microcad_std/
lib.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! µcad CLI install command.
5
6use std::path::PathBuf;
7
8use rust_embed::RustEmbed;
9use thiserror::Error;
10
11use crate::manifest::{Manifest, ManifestError};
12
13mod manifest;
14
15/// Standard library error.
16#[derive(Debug, Error)]
17pub enum StdLibError {
18    /// An error while processing the `manifest.toml` file.
19    #[error("An error while processing manifest file: {0}")]
20    ManifestError(#[from] manifest::ManifestError),
21
22    /// Error during install or uninstall.
23    #[error("An error during installation: {0}")]
24    InstallError(#[from] std::io::Error),
25}
26
27/// The µcad standard library asset.
28#[derive(RustEmbed)]
29#[folder = "lib/std"]
30pub struct StdLibEmbedded;
31
32/// An instance of the standard library.
33pub struct StdLib {
34    /// Path of the library which `manifest.toml`.
35    pub path: std::path::PathBuf,
36    /// The parsed manifest.
37    pub manifest: manifest::Manifest,
38}
39
40impl StdLib {
41    /// Create a new standard library instance from a path.
42    ///
43    /// Installs the standard library, if it is not installed.
44    pub fn new(path: impl AsRef<std::path::Path>) -> Result<Self, StdLibError> {
45        let path = PathBuf::from(path.as_ref());
46
47        let manifest = match manifest::Manifest::load(&path) {
48            Ok(manifest) => manifest,
49            // Try to install the standard library, in case the `manifold.toml`` has not been found.
50            Err(ManifestError::NotFound { path }) => Self::install(&path)?,
51            Err(err) => return Err(err.into()),
52        };
53
54        let manifest = if manifest.library.version != crate::version() {
55            eprintln!(
56                "µcad standard library version mismatch: {} != {}",
57                manifest.library.version,
58                crate::version()
59            );
60
61            // Handle version mismatch, force re-install
62            Self::reinstall(true)?
63        } else {
64            manifest
65        };
66
67        Ok(Self { path, manifest })
68    }
69
70    /// Try to reinstall into default path.
71    pub fn reinstall(force: bool) -> Result<Manifest, StdLibError> {
72        let path = Self::default_path();
73        if force {
74            Self::uninstall(&path)?;
75        }
76
77        Self::install(path)
78    }
79
80    /// Install the standard library into the standard library path and return its manifest.
81    fn install(path: impl AsRef<std::path::Path>) -> Result<manifest::Manifest, StdLibError> {
82        let path = path.as_ref();
83        eprintln!(
84            "Installing µcad standard library {} into {:?}...",
85            crate::version(),
86            path
87        );
88
89        std::fs::create_dir_all(path)?;
90
91        // Extract all embedded files.
92        StdLibEmbedded::iter().try_for_each(|file| {
93            let file_path = path.join(file.as_ref());
94            if let Some(parent) = file_path.parent() {
95                std::fs::create_dir_all(parent)?;
96            }
97            std::fs::write(
98                file_path,
99                StdLibEmbedded::get(file.as_ref())
100                    .expect("embedded folder 'lib/std'")
101                    .data,
102            )
103        })?;
104
105        // Write manifest file.
106        Manifest::default().save(path)?;
107
108        eprintln!("Successfully installed µcad standard library.");
109
110        Ok(manifest::Manifest::load(path)?)
111    }
112
113    /// Uninstall the standard library from the standard library path.
114    fn uninstall(path: impl AsRef<std::path::Path>) -> std::io::Result<()> {
115        let path = path.as_ref();
116
117        if !path.exists() {
118            eprintln!(
119                "µcad standard library not found in {:?}. Nothing to uninstall.",
120                path
121            );
122            return Ok(());
123        }
124
125        eprintln!("Removing µcad standard library from {:?}...", path);
126
127        // Extract all embedded files.
128        StdLibEmbedded::iter().try_for_each(|file| {
129            let file_path = path.join(file.as_ref());
130            std::fs::remove_file(file_path)
131        })?;
132
133        std::fs::remove_file(Manifest::manifest_path(path))?;
134
135        // All standard library files should been remove now.
136        std::fs::remove_dir(path)?;
137
138        eprintln!("Successfully uninstalled µcad standard library.");
139
140        Ok(())
141    }
142
143    /// Global library search path + `./std`.
144    pub fn default_path() -> std::path::PathBuf {
145        global_library_search_path().join("std")
146    }
147}
148
149/// The global library search path.
150///
151/// Use the config directory in home directory `.config/microcad` in Release mode.
152/// In Debug mode, the local directory `./crates/std/lib` is used.
153pub fn global_library_search_path() -> std::path::PathBuf {
154    #[cfg(not(debug_assertions))]
155    return dirs::config_dir()
156        .expect("config directory")
157        .join("microcad")
158        .join("lib");
159
160    #[cfg(debug_assertions)]
161    return std::path::PathBuf::from("./crates/std/lib");
162}
163
164/// Return the version number of this crate.
165pub fn version() -> semver::Version {
166    use std::str::FromStr;
167    semver::Version::from_str(env!("CARGO_PKG_VERSION")).expect("Valid version")
168}