recipe_reader/
lib.rs

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/*
10** Tests
11*/
12
13#[cfg(test)]
14mod tests;
15
16/*
17** Types
18*/
19
20#[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, //used during declaration
45}
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
75/*
76** Impls
77*/
78
79impl 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							//check for extra tokens
209							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							//check for extra tokens
251							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							//check for extra tokens
279							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							//check for extra tokens
291							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							//check for extra tokens
303							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							//check for extra tokens
315							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							//check for extra tokens
327							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							//check for extra tokens
339							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		/*TargetOptions
528		{
529			deps: false,
530			refs: false,
531			nolibc: false,
532			generate_c: false,
533			generate_ir: false,
534			lib_use: Vec::new(),
535			export: Vec::new(),
536			config: Vec::new(),
537			warnings: Vec::new(),
538		}*/
539	}
540}