1#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
6
7use std::fmt;
8use std::fs;
9use std::path::{Path, PathBuf};
10
11use presolve_compiler::platform::WorkspaceConfiguration;
12
13use crate::decode_cli_workspace_configuration_bytes_v1;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum CliExitCodeV1 {
17 Success = 0,
18 CompilationFailure = 1,
19 ConfigurationError = 2,
20 WorkspaceError = 3,
21 CompilerInternalError = 4,
22 CacheError = 5,
23 ToolingError = 6,
24 UnexpectedPlatformError = 7,
25}
26impl CliExitCodeV1 {
27 #[must_use]
28 pub const fn as_i32(self) -> i32 {
29 self as i32
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum CliCommandV1 {
35 Create,
36 Dev,
37 Build,
38 Watch,
39 Check,
40 Clean,
41 Explain,
42 Inspect,
43 Graph,
44 Trace,
45 Profile,
46 Benchmark,
47 Doctor,
48 Cache,
49 Workspace,
50 Version,
51}
52impl CliCommandV1 {
53 #[must_use]
54 pub const fn as_str(self) -> &'static str {
55 match self {
56 Self::Create => "create",
57 Self::Dev => "dev",
58 Self::Build => "build",
59 Self::Watch => "watch",
60 Self::Check => "check",
61 Self::Clean => "clean",
62 Self::Explain => "explain",
63 Self::Inspect => "inspect",
64 Self::Graph => "graph",
65 Self::Trace => "trace",
66 Self::Profile => "profile",
67 Self::Benchmark => "benchmark",
68 Self::Doctor => "doctor",
69 Self::Cache => "cache",
70 Self::Workspace => "workspace",
71 Self::Version => "version",
72 }
73 }
74}
75
76#[must_use]
77pub fn parse_cli_command_v1(value: &str) -> Option<CliCommandV1> {
78 match value {
79 "create" => Some(CliCommandV1::Create),
80 "dev" => Some(CliCommandV1::Dev),
81 "build" => Some(CliCommandV1::Build),
82 "watch" => Some(CliCommandV1::Watch),
83 "check" => Some(CliCommandV1::Check),
84 "clean" => Some(CliCommandV1::Clean),
85 "explain" => Some(CliCommandV1::Explain),
86 "inspect" => Some(CliCommandV1::Inspect),
87 "graph" => Some(CliCommandV1::Graph),
88 "trace" => Some(CliCommandV1::Trace),
89 "profile" => Some(CliCommandV1::Profile),
90 "benchmark" => Some(CliCommandV1::Benchmark),
91 "doctor" => Some(CliCommandV1::Doctor),
92 "cache" => Some(CliCommandV1::Cache),
93 "workspace" => Some(CliCommandV1::Workspace),
94 "version" => Some(CliCommandV1::Version),
95 _ => None,
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct CliProjectEnvelopeV1 {
101 pub project_root: PathBuf,
102 pub configuration_path: PathBuf,
103 pub configuration: WorkspaceConfiguration,
104}
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct CliProjectEnvelopeErrorV1 {
107 pub code: &'static str,
108 pub message: String,
109}
110impl fmt::Display for CliProjectEnvelopeErrorV1 {
111 fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
112 write!(output, "{}: {}", self.code, self.message)
113 }
114}
115impl std::error::Error for CliProjectEnvelopeErrorV1 {}
116
117pub fn load_explicit_project_envelope_v1(
121 project_root: &Path,
122 configuration_path: &Path,
123) -> Result<CliProjectEnvelopeV1, CliProjectEnvelopeErrorV1> {
124 let bytes = fs::read(configuration_path).map_err(|error| CliProjectEnvelopeErrorV1 {
125 code: "L9P001_CONFIGURATION_READ_FAILED",
126 message: format!("failed to read {}: {error}", configuration_path.display()),
127 })?;
128 let configuration = decode_cli_workspace_configuration_bytes_v1(&bytes).map_err(|error| {
129 CliProjectEnvelopeErrorV1 {
130 code: "L9P002_CONFIGURATION_DECODE_FAILED",
131 message: error.to_string(),
132 }
133 })?;
134 Ok(CliProjectEnvelopeV1 {
135 project_root: project_root.into(),
136 configuration_path: configuration_path.into(),
137 configuration,
138 })
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 #[test]
145 fn l9b_command_names_and_exit_codes_are_stable() {
146 assert_eq!(parse_cli_command_v1("version"), Some(CliCommandV1::Version));
147 assert_eq!(parse_cli_command_v1("unknown"), None);
148 assert_eq!(CliExitCodeV1::ConfigurationError.as_i32(), 2);
149 assert_eq!(CliCommandV1::Workspace.as_str(), "workspace");
150 }
151 #[test]
152 fn l9b_loader_reads_only_the_explicit_configuration_document() {
153 let root = std::env::temp_dir().join("presolve-l9b-envelope");
154 let _ = fs::remove_dir_all(&root);
155 fs::create_dir_all(&root).unwrap();
156 let config = root.join("chosen.json");
157 fs::write(
158 &config,
159 include_bytes!("../fixtures/configuration/minimum-cli-v1.json"),
160 )
161 .unwrap();
162 let envelope = load_explicit_project_envelope_v1(&root, &config).unwrap();
163 assert_eq!(envelope.configuration, WorkspaceConfiguration::default());
164 assert_eq!(envelope.configuration_path, config);
165 fs::remove_dir_all(root).unwrap();
166 }
167}