Skip to content

Commit dbb081a

Browse files
authored
fix(ml-cellar): add MLBin struct (#20)
Signed-off-by: scepter914 <scepter914@gmail.com>
1 parent 153c135 commit dbb081a

13 files changed

Lines changed: 736 additions & 113 deletions

File tree

src/bin/ml-cellar/check.rs

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
use ml_cellar::rack::{RackConfig, load_config};
2-
31
use std::path::{Path, PathBuf};
42
use walkdir::WalkDir;
53

6-
pub fn check_required_files(config: &RackConfig, target_directory_path: &Path) {
4+
use ml_cellar::cellar::load_cellar_config;
5+
use ml_cellar::ml_bin::MLBin;
6+
use ml_cellar::rack::{RackConfig, load_rack_config};
7+
8+
/// Checks that all required files defined in the rack configuration exist in the ML-bin.
9+
fn check_required_files(config: &RackConfig, ml_bin: &MLBin) {
710
for required_filename in &config.artifact.required_files {
8-
let required_file_path = target_directory_path.join(required_filename);
11+
let required_file_path = ml_bin.get_full_path().join(required_filename);
912
if !required_file_path.exists() {
1013
log::error!("Required file of {:?} is missing", required_file_path);
1114
std::process::exit(1);
@@ -14,14 +17,16 @@ pub fn check_required_files(config: &RackConfig, target_directory_path: &Path) {
1417
log::info!("All required files are confirmed");
1518
}
1619

17-
pub fn check_optional_files(config: &RackConfig, target_directory_path: &PathBuf) {
18-
let files_in_target_directory: Vec<PathBuf> = WalkDir::new(target_directory_path)
20+
/// Checks that all files in the ML-bin are either required or optional files
21+
/// as defined in the rack configuration.
22+
fn check_optional_files(config: &RackConfig, ml_bin: &MLBin) {
23+
let files_in_target_directory: Vec<PathBuf> = WalkDir::new(ml_bin.get_full_path())
1924
.into_iter()
2025
.filter_map(|e| e.ok())
2126
.filter(|p| p.file_type().is_file())
2227
.filter_map(|e| {
2328
e.path()
24-
.strip_prefix(target_directory_path)
29+
.strip_prefix(ml_bin.get_full_path())
2530
.ok()
2631
.map(Path::to_path_buf)
2732
})
@@ -39,39 +44,54 @@ pub fn check_optional_files(config: &RackConfig, target_directory_path: &PathBuf
3944
log::info!("All files are required files or optional files");
4045
}
4146

42-
pub fn check_version(config: &RackConfig, config_dir: &PathBuf, target_directory_path: &Path) {
43-
let target_directory_path_from_config_dir = target_directory_path
44-
.strip_prefix(config_dir)
45-
.unwrap_or(target_directory_path)
46-
.to_path_buf();
47+
/// Checks that the ML-bin version follows the valid version format
48+
/// defined in the project configuration.
49+
fn check_version(config: &RackConfig, ml_bin: &MLBin) {
4750
if config
4851
.project
49-
.is_valid_version(&target_directory_path_from_config_dir)
52+
.is_valid_version(ml_bin.project_name.to_str().unwrap(), &ml_bin.version)
5053
{
5154
log::info!("Version format is valid");
5255
} else {
5356
log::error!(
5457
"Version format is invalid at {:?}.\n
5558
Please check ProjectConfig of {:?} ",
56-
target_directory_path_from_config_dir,
59+
ml_bin.get_full_path(),
5760
config.project
5861
);
5962
std::process::exit(1);
6063
}
6164
}
6265

63-
pub fn check(target_directory_path: PathBuf) {
64-
if !target_directory_path.exists() {
65-
log::error!("{:?} does not exist", target_directory_path);
66+
/// Performs comprehensive validation checks on an ML-bin directory.
67+
///
68+
/// This function validates that:
69+
/// - All required files exist in the ML-bin
70+
/// - All files in the ML-bin are either required or optional
71+
/// - The version format follows the project's versioning rules
72+
///
73+
/// # Arguments
74+
///
75+
/// - `path` - Path to the ML-bin directory to check
76+
///
77+
/// # Panics
78+
///
79+
/// Exits the process with code 1 if:
80+
/// - The path does not exist
81+
/// - Any validation check fails
82+
///
83+
pub fn check(path: PathBuf) {
84+
if !path.exists() {
85+
log::error!("{:?} does not exist", path);
6686
std::process::exit(1);
67-
} else {
68-
log::info!(
69-
"Checking artifacts in rack at {:?}",
70-
target_directory_path.display()
71-
);
72-
let (config, config_dir) = load_config(&target_directory_path);
73-
check_required_files(&config, &target_directory_path);
74-
check_optional_files(&config, &target_directory_path);
75-
check_version(&config, &config_dir, &target_directory_path);
7687
}
88+
log::info!("Checking artifacts in rack at {:?}", path);
89+
90+
let (rack_config, rack_directory) = load_rack_config(&path);
91+
let (_cellar_config, cellar_directory) = load_cellar_config(&path);
92+
let ml_bin = MLBin::from_ml_bin_path(path, cellar_directory, rack_directory);
93+
94+
check_required_files(&rack_config, &ml_bin);
95+
check_optional_files(&rack_config, &ml_bin);
96+
check_version(&rack_config, &ml_bin);
7797
}

src/bin/ml-cellar/clone.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
use std::process::{Command, Stdio};
22

3+
/// Clones a Git repository without downloading Git LFS file contents.
4+
///
5+
/// This function clones a Git repository with `GIT_LFS_SKIP_SMUDGE=1` environment variable set,
6+
/// which means Git LFS pointers are cloned instead of the actual large files.
7+
/// This is useful for creating a lightweight clone of a model registry where you can
8+
/// selectively materialize only the files you need later.
9+
///
10+
/// # Arguments
11+
///
12+
/// - `repository` - The Git repository URL to clone
13+
///
14+
/// # Panics
15+
///
16+
/// Exits the process with code 1 if:
17+
/// - The git clone command fails
18+
/// - Git is not installed or cannot be executed
19+
///
320
pub fn clone(repository: String) {
421
let status = Command::new("git")
522
.env("GIT_LFS_SKIP_SMUDGE", "1")

src/bin/ml-cellar/docs.rs

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use regex::Regex;
22
use serde_json::Value;
33
use std::path::{Path, PathBuf};
44

5-
use ml_cellar::rack::{RackConfig, load_config};
5+
use ml_cellar::rack::{RackConfig, load_rack_config};
66

7+
/// Looks up a value in a JSON object using a dot-separated path.
8+
/// This function traverses a JSON structure using a path string like "a.b.c"
9+
/// to access nested fields. It supports both object fields and array indices.
710
fn lookup_json_path<'a>(root: &'a Value, path: &str) -> Option<&'a Value> {
811
let mut cur = root;
912

@@ -24,6 +27,7 @@ fn lookup_json_path<'a>(root: &'a Value, path: &str) -> Option<&'a Value> {
2427
Some(cur)
2528
}
2629

30+
/// Converts a JSON value to a text string representation.
2731
fn value_to_text(v: &Value) -> String {
2832
match v {
2933
// Delete " "
@@ -38,7 +42,11 @@ fn value_to_text(v: &Value) -> String {
3842
}
3943
}
4044

41-
/// Replace {a.b.c} in template file to JSON value
45+
/// Replaces placeholders in a markdown template with values from JSON data.
46+
/// This function finds all placeholders in the format `{a.b.c}` in the template
47+
/// and replaces them with corresponding values from the JSON data.
48+
/// If a placeholder is not found in the JSON, a warning is logged and the
49+
/// placeholder is kept as-is in the output.
4250
fn render_markdown_template(template: &str, data: &Value) -> String {
4351
let re = Regex::new(r"\{([^{}]+)\}").expect("invalid regex");
4452

@@ -57,6 +65,7 @@ fn render_markdown_template(template: &str, data: &Value) -> String {
5765
.to_string()
5866
}
5967

68+
/// Loads the template markdown file specified in the rack configuration.
6069
fn load_template_file(config: &RackConfig, config_dir: &Path) -> String {
6170
let template_file_path = config_dir.join(config.document.template_file.as_ref().unwrap());
6271
match std::fs::read_to_string(&template_file_path) {
@@ -68,7 +77,10 @@ fn load_template_file(config: &RackConfig, config_dir: &Path) -> String {
6877
}
6978
}
7079

71-
fn load_result_json(model_version_path: &Path, config: &RackConfig, config_dir: &Path) -> Value {
80+
/// Loads and parses the result JSON file from the ML-bin directory.
81+
/// This function loads the JSON file containing evaluation results and metrics
82+
/// for the ML model, as specified in the rack configuration.
83+
fn load_result_json(ml_bin_path: &Path, config: &RackConfig, config_dir: &Path) -> Value {
7284
if config.document.result_file.is_none() {
7385
log::error!(
7486
"Documentation configuration is not set up in {:?}.\n
@@ -78,7 +90,7 @@ fn load_result_json(model_version_path: &Path, config: &RackConfig, config_dir:
7890
std::process::exit(1);
7991
}
8092

81-
let result_file_path = model_version_path.join(config.document.result_file.as_ref().unwrap());
93+
let result_file_path = ml_bin_path.join(config.document.result_file.as_ref().unwrap());
8294

8395
let json_text = match std::fs::read_to_string(&result_file_path) {
8496
Ok(s) => s,
@@ -96,20 +108,40 @@ fn load_result_json(model_version_path: &Path, config: &RackConfig, config_dir:
96108
}
97109
}
98110

99-
pub fn docs(model_version_path: PathBuf) {
100-
if !model_version_path.exists() {
101-
log::error!("{:?} does not exist", model_version_path);
111+
/// Generates documentation for an ML-bin by rendering a template with result data.
112+
///
113+
/// This function:
114+
/// 1. Loads the rack configuration
115+
/// 2. Loads the result JSON file from the ML-bin
116+
/// 3. Loads the template markdown file
117+
/// 4. Replaces `{{version}}` with the full model version path
118+
/// 5. Replaces `{field.name}` placeholders with values from the result JSON
119+
/// 6. Prints the rendered documentation
120+
///
121+
/// # Arguments
122+
///
123+
/// - `ml_bin_path` - Path to the ML-bin directory to generate documentation for
124+
///
125+
/// # Panics
126+
///
127+
/// Exits the process with code 1 if:
128+
/// - The ML-bin path does not exist
129+
/// - The configuration or result files cannot be loaded
130+
/// - Template rendering fails
131+
pub fn docs(ml_bin_path: PathBuf) {
132+
if !ml_bin_path.exists() {
133+
log::error!("{:?} does not exist", ml_bin_path);
102134
std::process::exit(1);
103135
}
104136

105-
let (config, config_dir) = load_config(&model_version_path);
106-
let json_value = load_result_json(&model_version_path, &config, &config_dir);
137+
let (config, config_dir) = load_rack_config(&ml_bin_path);
138+
let json_value = load_result_json(&ml_bin_path, &config, &config_dir);
107139
let template_text = load_template_file(&config, &config_dir);
108140

109141
// Replace {{version}} with model version
110-
let path_from_config_dir = model_version_path
142+
let path_from_config_dir = ml_bin_path
111143
.strip_prefix(config_dir)
112-
.unwrap_or(&model_version_path)
144+
.unwrap_or(&ml_bin_path)
113145
.to_path_buf();
114146
let project_name = match path_from_config_dir.parent().and_then(|p| p.to_str()) {
115147
Some(s) => s.to_string(),
@@ -127,7 +159,7 @@ pub fn docs(model_version_path: PathBuf) {
127159
let rendered = render_markdown_template(&rendered_version, &json_value);
128160
log::info!(
129161
"Generate documentation for model version at path: {:?}",
130-
model_version_path
162+
ml_bin_path
131163
);
132164
print!("\n\n {rendered} \n\n");
133165
}

src/bin/ml-cellar/init.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
use std::process::{Command, Stdio};
22
use toml::to_string_pretty;
33

4+
use ml_cellar::cellar::CellarConfig;
45
use ml_cellar::file::GITATTRIBUTES;
5-
use ml_cellar::repository::RepositoryConfig;
66

7+
/// Initializes a new ml-cellar model registry repository.
8+
///
9+
/// This function sets up a new Git repository configured for use with ml-cellar by:
10+
/// 1. Initializing a new Git repository (`git init`)
11+
/// 2. Renaming the default branch to "main" (`git branch -M main`)
12+
/// 3. Creating a `.gitattributes` file with Git LFS configuration for common ML file types
13+
/// 4. Creating a `.mlcellar.toml` configuration file with default settings
14+
///
15+
/// The `.gitattributes` file configures Git LFS to track large files commonly used in ML projects,
16+
/// such as model checkpoints, weights, and data files.
17+
///
18+
/// # Panics
19+
///
20+
/// Exits the process with code 1 if:
21+
/// - Git is not installed or cannot be executed
22+
/// - `git init` or `git branch -M main` commands fail
23+
/// - `.gitattributes` or `.mlcellar.toml` files cannot be written
24+
///
725
pub fn init() {
826
// Git init
927
let status = Command::new("git")
@@ -59,8 +77,7 @@ pub fn init() {
5977
std::fs::write(".gitattributes", GITATTRIBUTES).expect("failed to write .gitattributes");
6078

6179
// Make .mlcellar.toml
62-
let config = RepositoryConfig::default();
63-
let toml_str =
64-
to_string_pretty(&config).expect("failed to serialize repository config to TOML");
80+
let config = CellarConfig::default();
81+
let toml_str = to_string_pretty(&config).expect("failed to serialize cellar config to TOML");
6582
std::fs::write(".mlcellar.toml", toml_str).expect("failed to write .mlcellar.toml");
6683
}

0 commit comments

Comments
 (0)