1#![allow(dead_code)]
2#![allow(unknown_lints)]
3#![allow(cyclomatic_complexity)]
4use std::path::{Path, PathBuf};
5use std::fs::File;
6use std::io::*;
7use std::env::{current_dir, set_current_dir};
8
9#[cfg(test)]
14mod tests;
15
16#[derive(Debug, Default)]
21pub struct Recipe
22{
23 pub ok: bool,
24 pub path: PathBuf,
25 pub target_count: u64,
26 pub targets: Vec<Target>
27}
28
29#[derive(Clone, Debug, Default)]
30pub struct Target
31{
32 pub name: String,
33 pub kind: TargetType,
34 pub files: Vec<String>,
35 pub options: TargetOptions,
36}
37
38#[derive(Copy, Clone, Debug, PartialEq, Eq)]
39pub enum TargetType
40{
41 SharedLib,
42 StaticLib,
43 Executable,
44 Temporary, }
46
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum Use
49{
50 Static,
51 Dynamic,
52}
53
54pub enum ReadState
55{
56 Start,
57 InsideTarget,
58}
59
60#[derive(Clone, Debug, Default)]
61pub struct TargetOptions
62{
63 pub deps: bool,
64 pub refs: bool,
65 pub nolibc: bool,
66 pub generate_c: bool,
67 pub generate_ir: bool,
68 pub lib_use: Vec<(String, Use)>,
69 pub export: Vec<String>,
70 pub config: Vec<String>,
71 pub warnings: Vec<String>,
72}
73
74
75impl ToString for TargetType
80{
81 fn to_string(&self) -> String
82 {
83 match *self
84 {
85 TargetType::Executable => "executable".to_string(),
86 TargetType::SharedLib => "shared".to_string(),
87 TargetType::StaticLib => "static".to_string(),
88 TargetType::Temporary => panic!("temporary target type is not allowed to be stringified"),
89 }
90 }
91}
92
93impl Default for TargetType
94{
95 fn default() -> TargetType
96 {
97 TargetType::Temporary
98 }
99}
100
101impl ToString for Use
102{
103 fn to_string(&self) -> String
104 {
105 match *self
106 {
107 Use::Static => "static".to_string(),
108 Use::Dynamic => "dynamic".to_string(),
109 }
110 }
111}
112
113impl Recipe
114{
115 pub fn new() -> Recipe
116 {
117 let mut temp: Recipe = Default::default();
118 temp.ok = true;
119 temp
120 }
121
122 pub fn find() -> Option<String>
123 {
124 let cwd = current_dir().unwrap();
125 let mut path = cwd.as_path();
126 let mut recipe = path.join(Path::new("recipe.txt"));
127
128 loop
129 {
130 let recipe_f = File::open(&recipe);
131 if recipe_f.is_err()
132 {
133 match path.parent()
134 {
135 Some(p) =>
136 {
137 path = p;
138 recipe = path.join(Path::new("recipe.txt"));
139 },
140 None => return None
141 }
142 }
143 else
144 {
145 return Some(recipe.into_os_string().into_string().unwrap());
146 }
147 }
148 }
149
150 pub fn read(&mut self)
151 {
152 self.read_errors(false);
153 }
154
155 pub fn read_errors(&mut self, errors: bool)
156 {
157 self.ok = false;
158 self.path = match Recipe::find()
159 {
160 Some(p) => PathBuf::from(p),
161 None =>
162 {
163 if errors {println!("error: recipe file not found in current path")};
164 return;
165 }
166 };
167 let mut recipe_file = match File::open(&self.path)
168 {
169 Ok(f) => f,
170 Err(_) =>
171 {
172 if errors {println!("error: could not open recipe file")};
173 return;
174 }
175 };
176
177 let mut contents = String::new();
178 recipe_file.read_to_string(&mut contents).unwrap();
179
180 let mut target = Target::new();
181 let mut state = ReadState::Start;
182 let mut line_number = 0;
183 for line in contents.lines()
184 {
185 line_number += 1;
186 if line.starts_with('#') { continue; }
187 let mut tokens = line.split_whitespace();
188 match state
189 {
190 ReadState::Start => if let Some(x) = tokens.next()
191 {
192 match x
193 {
194 "executable" =>
195 {
196 target.kind = TargetType::Executable;
197 target.name = match tokens.next()
198 {
199 Some(s) => s.to_string(),
200 None =>
201 {
202 if errors
203 {println!("error: expected target identifier at line {}", line_number);}
204 return;
205 }
206 };
207
208 if let Some(s) = tokens.next()
210 {
211 if errors
212 {println!("error: unexpected token '{}' at line '{}'", s, line_number);}
213 return;
214 }
215 state = ReadState::InsideTarget;
216 }
217 "lib" =>
218 {
219 target.name = match tokens.next()
220 {
221 Some(s) => s.to_string(),
222 None =>
223 {
224 if errors
225 {println!("error: expected target identifier at line {}", line_number);}
226 return;
227 }
228 };
229 target.kind = match tokens.next()
230 {
231 Some(s) => match s
232 {
233 "shared" => TargetType::SharedLib,
234 "static" => TargetType::StaticLib,
235 x =>
236 {
237 if errors
238 {println!("error: uknown library type '{}' at line {}", x, line_number)}
239 return;
240 }
241 },
242 None =>
243 {
244 if errors
245 {println!("error: expected a library type at line {}", line_number)}
246 return ;
247 }
248 };
249
250 if let Some(s) = tokens.next()
252 {
253 if errors
254 {println!("error: unexpected token '{}' at line '{}'", s, line_number);}
255 return;
256 }
257 state = ReadState::InsideTarget;
258 }
259 x =>
260 {
261 if errors
262 {println!("error: unknown target type '{}' at line {}", x, line_number);}
263 return;
264 }
265 }
266 },
267 ReadState::InsideTarget => if let Some(s) = tokens.next()
268 {
269 match s
270 {
271 "end" =>
272 {
273 self.targets.push(target.clone());
274 self.target_count += 1;
275 target = Target::new();
276 state = ReadState::Start;
277
278 if let Some(s) = tokens.next()
280 {
281 if errors
282 {println!("error: unexpected token '{}' at line '{}'", s, line_number);}
283 return;
284 }
285 },
286 "$refs" =>
287 {
288 target.options.refs = true;
289
290 if let Some(s) = tokens.next()
292 {
293 if errors
294 {println!("error: unexpected token '{}' at line '{}'", s, line_number);}
295 return;
296 }
297 }
298 "$deps" =>
299 {
300 target.options.deps = true;
301
302 if let Some(s) = tokens.next()
304 {
305 if errors
306 {println!("error: unexpected token '{}' at line '{}'", s, line_number);}
307 return;
308 }
309 }
310 "$nolibc" =>
311 {
312 target.options.nolibc = true;
313
314 if let Some(s) = tokens.next()
316 {
317 if errors
318 {println!("error: unexpected token '{}' at line '{}'", s, line_number);}
319 return;
320 }
321 }
322 "$generate-ir" =>
323 {
324 target.options.generate_ir = true;
325
326 if let Some(s) = tokens.next()
328 {
329 if errors
330 {println!("error: unexpected token '{}' at line '{}'", s, line_number);}
331 return;
332 }
333 }
334 "$generate-c" =>
335 {
336 target.options.generate_c = true;
337
338 if let Some(s) = tokens.next()
340 {
341 if errors
342 {println!("error: unexpected token '{}' at line '{}'", s, line_number);}
343 return;
344 }
345 }
346 "$warnings" => while let Some(p) = tokens.next()
347 {
348 target.options.warnings.push(p.to_string());
349 },
350 "$export" => while let Some(p) = tokens.next()
351 {
352 target.options.export.push(p.to_string());
353 },
354 "$config" => while let Some(p) = tokens.next()
355 {
356 target.options.config.push(p.to_string());
357 },
358 "$use" => match tokens.next()
359 {
360 Some(name) =>
361 {
362 match tokens.next()
363 {
364 Some(use_type) => match use_type
365 {
366 "static" =>
367 {
368 if !target.options.lib_use.contains(&(name.to_string(), Use::Static))
369 {target.options.lib_use.push((name.to_string(), Use::Static));}
370 else
371 {
372 if errors
373 {println!("error: duplicate library use '{}' at line {}", name, line_number);}
374 return;
375 }
376 },
377 "dynamic" =>
378 {
379 if !target.options.lib_use.contains(&(name.to_string(), Use::Dynamic))
380 {target.options.lib_use.push((name.to_string(), Use::Dynamic));}
381 else
382 {
383 if errors
384 {println!("error: duplicate library use '{}' at line {}", name, line_number);}
385 return;
386 }
387 },
388 x =>
389 {
390 if errors
391 {println!("error: unknown library type '{}' at line {}", x, line_number);}
392 return;
393 }
394 },
395 None =>
396 {
397 if errors
398 {println!("error: missing library type after '{}' at line {}", name, line_number);}
399 return;
400 }
401 }
402 },
403 None =>
404 {
405 if errors
406 {println!("error: missing library name at line {}", line_number);}
407 return;
408 }
409 },
410 x =>
411 {
412 if !x.starts_with('$')
413 {
414 if !target.files.contains(&x.to_string())
415 {target.files.push(x.to_string());}
416 else
417 {
418 if errors
419 {println!("error: duplicate file '{}' at line {}", x, line_number);}
420 return;
421 }
422 }
423 else
424 {
425 if errors
426 {println!("error: unknown option '{}' at line {}", x, line_number);}
427 return;
428 }
429 }
430 }
431 }
432 }
433 }
434 self.ok = true;
435 }
436
437 pub fn write(&self)
438 {
439 self.write_path(&self.path);
440 }
441
442 pub fn write_path(&self, path: &PathBuf)
443 {
444 let mut f = File::create(path).unwrap();
445
446 writeln!(f, "# this file is generated by recipe-reader, it might be overwritten\n")
447 .expect("error: failed to write to recipe file");
448 for trg_ptr in &self.targets
449 {
450 let trg = &(*trg_ptr);
451 match trg.kind
452 {
453 TargetType::Executable => {writeln!(f, "executable {}", trg.name).expect("error: failed to write to recipe file");},
454 TargetType::SharedLib | TargetType::StaticLib => {writeln!(f, "lib {} {}", trg.name, trg.kind.to_string()).expect("error: failed to write to recipe file");},
455 TargetType::Temporary => panic!("error: temporary target type is invalid"),
456 }
457
458 if trg.options.generate_c {writeln!(f, "\t$generate-c").expect("error: failed to write to recipe file");}
459 if trg.options.generate_ir {writeln!(f, "\t$generate-ir").expect("error: failed to write to recipe file");}
460 if trg.options.nolibc {writeln!(f, "\t$nolibc").expect("error: failed to write to recipe file");}
461 if trg.options.deps {writeln!(f, "\t$deps").expect("error: failed to write to recipe file");}
462 if trg.options.refs {writeln!(f, "\t$refs").expect("error: failed to write to recipe file");}
463 if !trg.options.export.is_empty()
464 {
465 write!(f, "\t$export").expect("error: failed to write to recipe file");
466 for export in &trg.options.export
467 {
468 write!(f, " {}", export).expect("error: failed to write to recipe file");
469 }
470 write!(f, "\n").expect("error: failed to write to recipe file");
471 }
472 if !trg.options.config.is_empty()
473 {
474 write!(f, "\t$config").expect("error: failed to write to recipe file");
475 for config in &trg.options.config
476 {
477 write!(f, " {}", config).expect("error: failed to write to recipe file");
478 }
479 write!(f, "\n").expect("error: failed to write to recipe file");
480 }
481 if !trg.options.warnings.is_empty()
482 {
483 write!(f, "\t$warnings").expect("error: failed to write to recipe file");
484 for warning in &trg.options.warnings
485 {
486 write!(f, " {}", warning).expect("error: failed to write to recipe file");
487 }
488 write!(f, "\n").expect("error: failed to write to recipe file");
489 }
490 if !trg.options.lib_use.is_empty()
491 {
492 for lib_use in &trg.options.lib_use
493 {
494 writeln!(f, "\t$use {} {}", lib_use.0, lib_use.1.to_string()).expect("error: failed to write to recipe file");
495 }
496 }
497 for file in &trg.files
498 {
499 writeln!(f, "\t{}", file).expect("error: failed to write to recipe file");
500 }
501 writeln!(f, "end\n").expect("error: failed to write to recipe file");
502 }
503 }
504
505 pub fn chdir(&self)
506 {
507 set_current_dir(Path::new(&self.path)).expect("error: failed to chdir");
508 }
509
510 pub fn add_target(&mut self, trg: Target)
511 {
512 self.targets.push(trg);
513 self.target_count += 1;
514 }
515}
516
517impl Target
518{
519 fn new() -> Target { Default::default() }
520}
521
522impl TargetOptions
523{
524 pub fn new() -> TargetOptions
525 {
526 Default::default()
527 }
540}