flowlog_build/common/
config.rs1use clap::{Parser, ValueEnum};
4use std::path::{Path, PathBuf};
5use std::{fs, process};
6
7#[derive(Clone, Copy, Debug, Eq, PartialEq, ValueEnum, Default)]
9pub enum ExecutionMode {
10 #[default]
14 DatalogBatch,
15 DatalogInc,
19 ExtendBatch,
23 ExtendInc,
26}
27
28impl ExecutionMode {
29 pub(crate) fn is_incremental(self) -> bool {
30 matches!(self, Self::DatalogInc | Self::ExtendInc)
31 }
32
33 pub(crate) fn is_batch(self) -> bool {
34 matches!(self, Self::DatalogBatch | Self::ExtendBatch)
35 }
36}
37
38#[derive(Parser, Debug, Clone, Default)]
40#[command(version, about, long_about = None)]
41pub struct Config {
42 #[arg(value_name = "PROGRAM")]
44 pub program: String,
45
46 #[arg(short = 'F', long, value_name = "DIR")]
48 pub fact_dir: Option<String>,
49
50 #[arg(short = 'o', value_name = "PATH")]
52 pub executable_path: Option<String>,
53
54 #[arg(short = 'D', long, value_name = "DIR")]
56 pub output_dir: Option<String>,
57
58 #[arg(long, value_enum, default_value = "datalog-batch", value_name = "MODE")]
62 pub mode: ExecutionMode,
63
64 #[arg(long, short = 'P')]
66 pub profile: bool,
67
68 #[arg(long)]
71 pub sip: bool,
72
73 #[arg(long)]
77 pub str_intern: bool,
78
79 #[arg(long, value_name = "PATH")]
83 pub udf_file: Option<String>,
84
85 #[arg(long)]
88 pub save_temps: bool,
89
90 #[arg(short = 'I', long = "include-dir", value_name = "DIR")]
94 pub include_dirs: Vec<String>,
95}
96
97impl Config {
98 pub fn program(&self) -> &str {
99 &self.program
100 }
101
102 pub fn should_process_all(&self) -> bool {
103 self.program == "all" || self.program == "--all"
104 }
105
106 pub fn program_name(&self) -> String {
107 Path::new(&self.program)
108 .file_stem()
109 .and_then(|stem| stem.to_str())
110 .map(|s| s.to_string())
111 .unwrap_or_else(|| "unknown_program".into())
112 }
113
114 pub fn fact_dir(&self) -> Option<&str> {
115 self.fact_dir.as_deref()
116 }
117
118 pub fn executable_path(&self) -> PathBuf {
119 self.executable_path
120 .as_ref()
121 .map(PathBuf::from)
122 .unwrap_or_else(|| PathBuf::from(self.program_name()))
123 }
124
125 pub fn build_dir(&self) -> PathBuf {
129 let exe = self.executable_path();
130 let name = exe.file_name().and_then(|n| n.to_str()).unwrap_or("out");
131 exe.with_file_name(format!(".{name}.build"))
132 }
133
134 pub fn executable_name(&self) -> String {
135 self.executable_path()
136 .file_name()
137 .and_then(|name| name.to_str())
138 .unwrap_or("out")
139 .to_string()
140 }
141
142 pub fn crate_name(&self) -> String {
146 let raw = self.executable_name();
147 let mut s: String = raw
148 .chars()
149 .map(|c| {
150 if c.is_ascii_alphanumeric() || c == '_' || c == '-' {
151 c
152 } else {
153 '_'
154 }
155 })
156 .collect();
157 if s.starts_with(|c: char| c.is_ascii_digit()) {
159 s.insert_str(0, "fl_");
160 }
161 if s.is_empty() {
162 s = "out".to_string();
163 }
164 s
165 }
166
167 pub fn output_dir(&self) -> Option<&str> {
168 self.output_dir.as_deref()
169 }
170
171 pub fn output_to_stdout(&self) -> bool {
172 self.output_dir.as_deref() == Some("-")
173 }
174
175 pub fn mode(&self) -> ExecutionMode {
176 self.mode
177 }
178
179 pub fn is_incremental(&self) -> bool {
180 self.mode.is_incremental()
181 }
182
183 pub fn is_batch(&self) -> bool {
184 self.mode.is_batch()
185 }
186
187 pub fn is_datalog_batch(&self) -> bool {
190 self.mode == ExecutionMode::DatalogBatch
191 }
192
193 pub fn is_extended(&self) -> bool {
196 matches!(
197 self.mode,
198 ExecutionMode::ExtendBatch | ExecutionMode::ExtendInc
199 )
200 }
201
202 pub fn fact_dir_required(&self) -> &str {
204 self.fact_dir
205 .as_ref()
206 .expect("--fact-dir is required for this tool")
207 }
208
209 pub fn output_dir_required(&self) -> &str {
211 self.output_dir
212 .as_ref()
213 .expect("--output-dir is required for this tool")
214 }
215
216 pub fn profiling_enabled(&self) -> bool {
218 if self.profile && self.is_extended() {
219 unimplemented!("-P (profiling) is not yet supported with extended modes");
220 }
221 self.profile
222 }
223
224 pub fn sip_enabled(&self) -> bool {
226 self.sip
227 }
228
229 pub fn str_intern_enabled(&self) -> bool {
231 self.str_intern
232 }
233
234 pub fn udf_file(&self) -> Option<&str> {
236 self.udf_file.as_deref()
237 }
238
239 pub fn include_dirs(&self) -> Vec<&Path> {
241 self.include_dirs.iter().map(Path::new).collect()
242 }
243
244 pub fn save_temps(&self) -> bool {
246 self.save_temps
247 }
248}
249
250pub fn get_example_files() -> Vec<std::path::PathBuf> {
252 let example_dir = "example";
253
254 if !Path::new(example_dir).exists() {
256 eprintln!("Error: Directory '{}' not found", example_dir);
257 process::exit(1);
258 }
259
260 let mut files = Vec::new();
262 let mut dirs = vec![PathBuf::from(example_dir)];
263 while let Some(dir) = dirs.pop() {
264 let entries = match fs::read_dir(&dir) {
265 Ok(entries) => entries,
266 Err(e) => {
267 eprintln!("Error reading dir '{}': {}", dir.display(), e);
268 continue;
269 }
270 };
271 for entry in entries.flatten() {
272 let path = entry.path();
273 if path.is_dir() {
274 dirs.push(path);
275 } else if path.extension().and_then(|s| s.to_str()) == Some("dl") {
276 files.push(path);
277 }
278 }
279 }
280
281 files.sort();
282
283 if files.is_empty() {
284 eprintln!("No .dl files found in {}", example_dir);
285 process::exit(1);
286 }
287
288 files
289}