thinlinelib/
analysis.rs

1use entity::Entity;
2use failure::{err_msg, Fallible};
3use glob::glob;
4use language_type::LanguageType;
5use std::{
6    cell::{Ref, RefCell, RefMut}, fmt::{Display, Formatter, Result}, marker::PhantomData,
7    path::PathBuf,
8};
9
10////////////////////////////////////////////////////////////////////////////////
11
12/// Represents a entity description.
13#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct Description {
15    pub description: Vec<String>,
16}
17
18impl Description {
19    /// Creates a new Description instance.
20    pub fn new() -> Self {
21        Self {
22            description: Vec::new(),
23        }
24    }
25
26    /// Sets and formats the description.
27    pub fn set(&mut self, description: &str) {
28        self.description = description
29            .split('\n')
30            .map(|desc| {
31                String::from(
32                    desc.trim_left()
33                        .trim_left_matches('*')
34                        .trim_left_matches('/')
35                        .trim_left(),
36                )
37            })
38            .filter(|ref desc| !desc.is_empty() && desc.as_str() != "**")
39            .map(|desc| {
40                if desc.chars().next() == Some('#') {
41                    desc.replace(" ", "")
42                } else {
43                    desc
44                }
45            })
46            .collect();
47    }
48}
49
50////////////////////////////////////////////////////////////////////////////////
51
52/// Represents a parsed function argument.
53#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
54pub struct Argument {
55    pub name: String,
56    pub atype: Option<String>,
57    pub value: Option<String>,
58}
59
60impl Argument {
61    /// Creates a new Argument instance.
62    ///
63    /// # Example
64    ///
65    /// ```
66    /// use thinlinelib::analysis::Argument;
67    ///
68    /// let argument = Argument::new("int1", Some("int"));
69    ///
70    /// assert_eq!(argument.name, "int1");
71    /// assert!(argument.atype.is_some());
72    /// assert_eq!(argument.atype.unwrap(), "int");
73    /// ```
74    pub fn new<S: Into<String>>(name: S, atype: Option<S>) -> Self {
75        Argument {
76            name: name.into(),
77            atype: atype.map(S::into),
78            value: None,
79        }
80    }
81
82    /// Sets a value to the argument.
83    ///
84    /// # Example
85    ///
86    /// ```
87    /// use thinlinelib::analysis::Argument;
88    ///
89    /// let mut argument = Argument::new("arg", Some("std::string"));
90    /// argument.set_value("FirstArg");
91    ///
92    /// assert!(argument.value.is_some());
93    ///
94    /// ```
95    pub fn set_value(&mut self, value: &str) {
96        self.value = Some(String::from(value));
97    }
98}
99
100////////////////////////////////////////////////////////////////////////////////
101
102/// Represents a parsed function type.
103#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
104pub struct Function {
105    pub name: String,
106    pub return_type: Option<String>,
107    pub arguments: Vec<Argument>,
108    pub description: Option<Description>,
109}
110
111impl Function {
112    /// Creates a new Function instance.
113    ///
114    /// # Example
115    ///
116    /// ```
117    /// use thinlinelib::analysis::Function;
118    ///
119    /// let function = Function::new("testFunction");
120    ///
121    /// assert_eq!(function.name, String::from("testFunction"));
122    /// assert!(function.return_type.is_none());
123    /// assert!(function.arguments.is_empty());
124    /// assert!(function.description.is_none());
125    /// ```
126    pub fn new<S: Into<String>>(name: S) -> Self {
127        Self {
128            name: name.into(),
129            return_type: None,
130            arguments: Vec::new(),
131            description: None,
132        }
133    }
134
135    /// Creates the format type for the Function.
136    ///
137    /// # Example
138    ///
139    /// ```
140    /// use thinlinelib::analysis::Function;
141    ///
142    /// let mut function = Function::new("testFunction");
143    /// function.set_return_type("int");
144    ///
145    /// assert_eq!(function.return_type, Some(String::from("int")));
146    ///
147    /// function.set_return_type("");
148    ///
149    /// assert_eq!(function.return_type, None);
150    /// ```
151    pub fn set_return_type(&mut self, ftype: &str) -> Fallible<()> {
152        if ftype.is_empty() {
153            self.return_type = None;
154        } else {
155            let ftype_vec: Vec<&str> = ftype.split('(').collect();
156            self.return_type = Some(String::from(
157                ftype_vec
158                    .get(0)
159                    .ok_or_else(|| err_msg("Function type can not be parsed from signature."))?
160                    .trim_right(),
161            ));
162        }
163
164        Ok(())
165    }
166
167    /// Sets the description for the Function.
168    ///
169    /// # Example
170    ///
171    /// ```
172    /// use thinlinelib::analysis::Function;
173    ///
174    /// let mut function = Function::new("testFunction");
175    /// function.set_description("
176    /// # TESTCASE(check_if_sum_works)
177    ///    int test_no = 2;
178    ///    #EQ[TL_FCT(no1: test_no, no2: 5) => 7]
179    ///    #EQ[TL_FCT(no1: 5, no2: 2) => 7]
180    ///    EXPECT_EQ(11, test_int_no1(9, 2));
181    /// ");
182    ///
183    /// assert!(function.description.is_some());
184    /// ```
185    pub fn set_description(&mut self, description: &str) {
186        if self.description.is_none() {
187            self.description = Some(Description::new());
188        }
189
190        if let Some(desc) = &mut self.description {
191            desc.set(description);
192        }
193    }
194
195    /// Sets arguments for the Function.
196    pub fn set_arguments(&mut self, arguments: &[Argument]) {
197        self.arguments = arguments.into();
198    }
199}
200
201////////////////////////////////////////////////////////////////////////////////
202
203/// Represents a parsed enum argument.
204#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
205pub struct Enum {
206    pub name: String,
207    pub etype: Option<String>,
208    pub arguments: Vec<Argument>,
209}
210
211impl Enum {
212    /// Creates a new Enum instance.
213    ///
214    /// # Example
215    ///
216    /// ```
217    /// use thinlinelib::analysis::Enum;
218    ///
219    /// let enumeration = Enum::new("testEnum");
220    ///
221    /// assert_eq!(enumeration.name, String::from("testEnum"));
222    /// assert!(enumeration.etype.is_none());
223    /// assert!(enumeration.arguments.is_empty());
224    /// ```
225    pub fn new<S: Into<String>>(name: S) -> Self {
226        Self {
227            name: name.into(),
228            etype: None,
229            arguments: Vec::new(),
230        }
231    }
232
233    /// Sets arguments for the Enum.
234    ///
235    /// # Example
236    ///
237    /// ```
238    /// use thinlinelib::analysis::{Argument, Enum};
239    ///
240    /// let mut enumeration = Enum::new("testEnum");
241    /// let args = vec![Argument::new("Zero", Some("0")), Argument::new("Two", Some("2"))];
242    /// enumeration.set_arguments(&args);
243    ///
244    /// assert_eq!(enumeration.arguments.len(), 2);
245    /// ```
246    pub fn set_arguments(&mut self, arguments: &[Argument]) {
247        self.arguments = arguments.into();
248    }
249}
250
251////////////////////////////////////////////////////////////////////////////////
252
253#[derive(Default, Clone, Debug)]
254pub struct ProjectFile<T> {
255    pub pf_type: PhantomData<T>,
256    pub path: PathBuf,
257    pub entities: RefCell<Vec<Entity>>,
258}
259
260/// Represents a parsed project file.
261impl<T> ProjectFile<T>
262where
263    T: LanguageType,
264{
265    /// Creates a new ProjectFile instance.
266    ///
267    /// # Example
268    ///
269    /// ```
270    /// use std::path::PathBuf;
271    /// use thinlinelib::analysis::ProjectFile;
272    /// use thinlinelib::language_type::C;
273    ///
274    /// let project_file: ProjectFile<C> = ProjectFile::new("test/project_file");
275    ///
276    /// assert_eq!(project_file.path, PathBuf::from("test/project_file"));
277    /// assert_eq!(project_file.entities().len(), 0);
278    /// ```
279    pub fn new<S: Into<PathBuf>>(path: S) -> Self {
280        ProjectFile {
281            pf_type: PhantomData,
282            path: path.into(),
283            entities: RefCell::new(Vec::new()),
284        }
285    }
286
287    /// Returns a reference to the entities list.
288    ///
289    /// # Example
290    ///
291    /// ```
292    /// use thinlinelib::analysis::ProjectFile;
293    /// use thinlinelib::entity::Entity;
294    /// use thinlinelib::language_type::C;
295    ///
296    /// let project_file: ProjectFile<C> = ProjectFile::new("test/project_file");
297    /// project_file.entities_mut().push(Entity::new("testEntity"));
298    ///
299    /// assert_eq!(project_file.entities().len(), 1);
300    /// ```
301    pub fn entities(&self) -> Ref<Vec<Entity>> {
302        self.entities.borrow()
303    }
304
305    /// Returns a mutable reference to the entities list.
306    ///
307    /// # Example
308    ///
309    /// ```
310    /// use thinlinelib::analysis::ProjectFile;
311    /// use thinlinelib::entity::Entity;
312    /// use thinlinelib::language_type::C;
313    ///
314    /// let project_file: ProjectFile<C> = ProjectFile::new("test/project_file");
315    /// project_file.entities_mut().push(Entity::new("testEntity"));
316    ///
317    /// let mut entities = project_file.entities_mut();
318    /// assert_eq!(entities.len(), 1);
319    ///
320    /// entities.clear();
321    /// assert_eq!(entities.len(), 0);
322    /// ```
323    pub fn entities_mut(&self) -> RefMut<Vec<Entity>> {
324        self.entities.borrow_mut()
325    }
326}
327
328impl<T> Display for ProjectFile<T>
329where
330    T: LanguageType,
331{
332    /// Formats a ProjectFile to be displayed by std output.
333    fn fmt(&self, f: &mut Formatter) -> Result {
334        if let Some(path) = self.path.to_str() {
335            return write!(f, "{}", path);
336        }
337        write!(f, "Unable to stringify filename")
338    }
339}
340
341////////////////////////////////////////////////////////////////////////////////
342
343#[derive(Default, Debug)]
344pub struct Analysis<T>
345where
346    T: LanguageType,
347{
348    pub file_types: &'static [&'static str],
349    pub project_files: RefCell<Vec<ProjectFile<T>>>,
350}
351
352impl<T> Analysis<T>
353where
354    T: LanguageType,
355{
356    /// Creates a new Analysis instance.
357    ///
358    /// # Example
359    ///
360    /// ```
361    /// use thinlinelib::analysis::Analysis;
362    /// use thinlinelib::language_type::{C, LanguageType};
363    ///
364    /// let analysis: Analysis<C> = Analysis::new();
365    ///
366    /// assert_eq!(analysis.file_types, C::file_types());
367    /// assert_eq!(analysis.project_files().len(), 0);
368    /// ```
369    pub fn new() -> Self {
370        Analysis {
371            file_types: T::file_types(),
372            project_files: RefCell::new(Vec::new()),
373        }
374    }
375
376    /// Returns a reference to the collected project files for analysis.
377    pub fn project_files(&self) -> Ref<Vec<ProjectFile<T>>> {
378        self.project_files.borrow()
379    }
380
381    /// Returns a mutable reference to the collected project files for analysis.
382    ///
383    /// # Example
384    ///
385    /// ```
386    /// use thinlinelib::analysis::{Analysis, ProjectFile};
387    /// use thinlinelib::language_type::C;
388    ///
389    /// let analysis: Analysis<C> = Analysis::new();
390    /// let mut project_files = analysis.project_files_mut();
391    /// assert_eq!(project_files.len(), 0);
392    ///
393    /// project_files.push(ProjectFile::new("test/anotherFile"));
394    /// assert_eq!(project_files.len(), 1);
395    /// ```
396    pub fn project_files_mut(&self) -> RefMut<Vec<ProjectFile<T>>> {
397        self.project_files.borrow_mut()
398    }
399
400    /// Collects all the sources within the given project dir.
401    pub fn collect_sources(&self, project_dir: &PathBuf, search_dirs: &[String]) -> Fallible<()> {
402        // Check the given project directory
403        if !project_dir.exists() || !project_dir.is_dir() {
404            return Err(format_err!(
405                "The given project dir '{}' does not exist.",
406                project_dir
407                    .to_str()
408                    .ok_or_else(|| err_msg("Unable to stringify project dir path."))?
409            ));
410        }
411
412        // Traverse through the files within the specified source directories
413        // and store them for analyzing purposes
414        for src_dir in search_dirs {
415            for ext in self.file_types {
416                for entry in glob(
417                    project_dir
418                        .join(src_dir)
419                        .join("**")
420                        .join(String::from("*.") + ext)
421                        .to_str()
422                        .unwrap_or("."),
423                )? {
424                    self.project_files_mut().push(ProjectFile::new(entry?));
425                }
426            }
427        }
428
429        Ok(())
430    }
431
432    /// Extracts function signatures and comments of thinlines parsed files.
433    pub fn extract_entities(&self) -> Fallible<()> {
434        T::extract_entities(&self)
435    }
436}