cppbuild/
project.rs

1use clang::*;
2use serde_derive::{Deserialize, Serialize};
3use std::fs::{create_dir, File};
4use std::io::Read;
5use std::path::Path;
6use std::io::Write;
7use itertools::Itertools;
8use walkdir::WalkDir;
9//thinking of moving deps, and owners to package then putting examples and tests in Project
10#[derive(Debug, Serialize, Deserialize, Clone)]
11pub struct Package {
12    name: String,
13    version: String,
14    standard: String,
15    project_type: Option<String>,
16    repository: Option<String>,
17    owners: Vec<Owner>,
18    pub dependency: Option<Vec<Dependency>>,
19    dev_dependency: Option<Vec<Dependency>>,
20    description: Option<String>,
21    license: Option<String>,
22}
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Example {
25    exec_paths: Vec<String>,
26}
27impl Example {
28    pub fn new(path: &str) -> std::io::Result<Self> {
29        let mut exec_paths = Vec::new();
30        for file in WalkDir::new(format!("{}/examples", path)){
31            exec_paths.push(format!("{}", file?.path().display()));
32        }
33        Ok( Self { exec_paths } )
34    }
35    pub fn find(&self, name: &str) -> Option<String>{
36        let name = format!("examples/{}.cpp", name);
37        if self.exec_paths.contains(&name){
38            return Some(name);
39        }
40        None
41    }
42}
43/*pub struct Location{
44    file: String,
45    start: u32,
46    end: u32,
47}
48impl Location{
49    pub fn new()
50}*/
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
52pub enum ItemType{
53    FunctionDecl,
54    UsingDirective,
55    InclusionDirective,
56}
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
58pub struct Item {
59    name: Option<String>,
60    comment: Option<String>,
61    full_text: String,
62    kind: ItemType,
63}
64impl Item {
65    pub fn new(
66        name: Option<String>,
67        comment: Option<String>,
68        path: &str,
69        start: usize,
70        end: usize,
71        kind: ItemType,
72    ) -> std::result::Result<Self, std::io::Error> {
73        let mut file = File::open(path)?;
74        let mut content: Vec<u8> = Vec::new();
75        match file.read_to_end(&mut content) {
76            Ok(_) => (),
77            Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
78        };
79        let full_text: String = match String::from_utf8(content[start..end].to_vec()) {
80            Ok(text) => text,
81            Err(e) => return Err(std::io::Error::new(std::io::ErrorKind::Other, e)),
82        };
83        Ok(Self {
84            name,
85            comment,
86            full_text,
87            kind
88        })
89    }
90    pub fn get_type(&self) -> ItemType{
91        self.kind.clone()
92    }
93    pub fn get_text(&self) -> String{
94        self.full_text.clone()
95    }
96    pub fn get_name(&self) -> Option<String>{
97        self.name.clone()
98    }
99}
100#[derive(Debug, Serialize, Deserialize, Clone)]
101pub struct Test {
102    name: String,
103    entities: Vec<Item>,
104    dir: String,
105}
106impl Test {
107    ///reads from the file given in path and matches doc comments found to see if an identifier can be found, in the binary that is ///test
108    pub fn from_file(path: &str, ident: &str) -> std::result::Result<Self, std::io::Error> {
109        let project = Project::from_file(path)?;
110        let clang = Clang::new().unwrap();
111        let index = Index::new(&clang, false, false);
112        let mut tests: Vec<std::io::Result<String>> = WalkDir::new(format!("{}/src",path)).into_iter().filter(|e|{
113            e.as_ref().unwrap().path().is_file()
114        }).map(|e| {
115            Ok(format!("{}", e?.path().display()))
116        }).collect();
117        /*tests.append(&mut WalkDir::new(format!("{}/headers",path)).into_iter().filter(|e|{
118            e.as_ref().unwrap().path().is_file()
119        }).map(|e|{
120            Ok(format!("{}", e?.path().display()))
121        }).collect());*/
122        tests.append(&mut WalkDir::new(format!("{}/tests",path)).into_iter().filter(|e|{
123            e.as_ref().unwrap().path().is_file()
124        }).map(|e| {
125            Ok(format!("{}", e?.path().display()))
126        }).collect());
127        let mut funcs = Vec::new();
128        for test in tests.iter(){
129            let test = match test{
130                Ok(t) => t,
131                Err(e) => return Err(std::io::Error::new(e.kind(), "error getting test")),
132            };
133            let tu = index
134                .parser(test)
135                .detailed_preprocessing_record(true)
136                .parse()
137                .unwrap();
138            let functions = tu
139                .get_entity()
140                .get_children()
141                .into_iter()
142                .filter(|e|  { 
143                    e.get_kind() == EntityKind::FunctionDecl && e.get_comment() == Some(ident.to_string()) ||
144                    e.get_kind() == EntityKind::InclusionDirective && !e.is_in_system_header()
145                        || e.get_kind() == EntityKind::UsingDirective && !e.is_in_system_header()
146                })
147                .collect::<Vec<_>>();
148            for func in functions.iter() {
149                //println!("func: {:?}", func);
150                    //println!("start: {:?}, end: {:?}", func.get_range().unwrap().get_start(), func.get_range().unwrap().get_end());
151                    let range = func.get_range().unwrap();
152                    funcs.push(Item::new(
153                        func.get_display_name(),
154                        func.get_comment(),
155                        &format!(
156                            "{}",
157                            func.get_location()
158                                .unwrap()
159                                .get_file_location()
160                                .file
161                                .unwrap()
162                                .get_path()
163                                .as_path()
164                                .display()
165                        ),
166                        range.get_start().get_file_location().offset as usize,
167                        range.get_end().get_file_location().offset as usize,
168                        match func.get_kind(){
169                            EntityKind::FunctionDecl => ItemType::FunctionDecl,
170                            EntityKind::UsingDirective => ItemType::UsingDirective,
171                            _ => ItemType::InclusionDirective
172                        }
173                    )?);
174            }
175        }
176        Ok(Self {
177            name: project.get_name(),
178            entities: funcs.into_iter().unique().collect(),
179            dir: path.to_string(),
180        })
181    }
182    ///returns each test function from this Test instance
183    pub fn get_entities(&self) -> Vec<Item> {
184        self.entities.clone()
185    }
186    ///this function is responsible for consolidating all test functions in the project
187    pub fn append(&mut self, second: &mut Vec<Item>) {
188        self.entities.append(second);
189    }
190    ///this function builds the main function that calls each test function
191    pub fn build_main(&self) -> std::result::Result<(), std::io::Error> {
192        let mut file = File::create(format!("{}/target/test_{}.cpp", self.dir, self.name))?;
193        let mut open: bool = false;
194        let mut funcs = Vec::new();
195        for item in self.entities.iter(){
196            match item.get_type(){
197                ItemType::InclusionDirective => {
198                    file.write_all(format!("{}\n", item.get_text()).as_bytes())?
199                },
200                ItemType::UsingDirective => {
201                    file.write_all(format!("{};\n", item.get_text()).as_bytes())?
202                },
203                ItemType::FunctionDecl => {
204                    file.write_all(format!("int {};\n", item.get_name().unwrap()).as_bytes())?;
205                    funcs.push(item);
206                },
207            }
208        }
209        for func in funcs.iter(){
210            if !open{
211                open = true;
212                file.write_all(b"int main(){\n")?;
213            }
214            file.write_all(format!("\t{};\n", func.get_name().unwrap()).as_bytes())?;
215        }
216        file.write_all(b"}\n")?;
217        Ok(())
218    }
219}
220#[derive(Debug, Serialize, Deserialize, Clone)]
221pub struct Dependency {
222    name: String,
223    version: String,
224    url: Option<String>,
225}
226impl Dependency {
227    pub fn new(name: String, version: String, url: Option<String>) -> Self {
228        Self { name, version, url }
229    }
230    pub fn get_name(&self) -> String {
231        self.name.clone()
232    }
233    pub fn get_version(&self) -> String {
234        self.version.clone()
235    }
236    pub fn get_url(&self) -> Option<String> {
237        self.url.clone()
238    }
239}
240#[derive(Debug, Serialize, Deserialize, Clone)]
241pub struct Owner {
242    name: String,
243    email: String,
244}
245#[derive(Debug, Serialize, Deserialize)]
246pub struct Project {
247    package: Package,
248    examples: Option<Vec<Example>>,
249    tests: Option<Test>,
250}
251impl Project {
252    pub fn new(name: String, project_type: Option<String>, owners: Option<Vec<Owner>>) -> Self {
253        let mut own = Vec::new();
254        match owners {
255            Some(mut owns) => own.append(&mut owns),
256            None => own.push(Owner::new(whoami::username())),
257        }
258        Self {
259            package: Package::new(
260                name,
261                "0.1.0".to_string(),
262                "c++17".to_string(),
263                project_type,
264                own,
265                None,
266            ),
267            examples: None,
268            tests: None,
269        }
270    }
271    pub fn get_package(&self) -> Package {
272        self.package.clone()
273    }
274    pub fn get_dependencies(&self) -> Option<Vec<Dependency>> {
275        match &self.package.dependency {
276            Some(dep) => Some(dep.clone()),
277            None => None,
278        }
279    }
280    pub fn get_type(&self) -> String {
281        self.package.project_type.as_ref().unwrap().clone()
282    }
283    pub fn get_version(&self) -> String {
284        self.package.version.clone()
285    }
286    pub fn get_name(&self) -> String {
287        self.package.name.clone()
288    }
289    pub fn get_standard(&self) -> String {
290        self.package.standard.clone()
291    }
292    pub fn from_file(path: &str) -> std::io::Result<Self> {
293        let mut file = match File::open(format!("{}/{}", path, "build.toml")) {
294            Ok(file) => file,
295            Err(e) => return Err(e),
296        };
297        if !Path::new("target/").exists() {
298            match create_dir("target") {
299                Ok(()) => (),
300                Err(e) => return Err(e),
301            }
302        }
303        let mut content = String::new();
304        match file.read_to_string(&mut content) {
305            Ok(_) => (),
306            Err(e) => return Err(e),
307        }
308        Ok(Self {
309            package: toml::from_str(content.as_str()).unwrap(),
310            examples: None,
311            tests: None,
312        })
313    }
314}
315
316impl Package {
317    pub fn new(
318        name: String,
319        version: String,
320        standard: String,
321        project_type: Option<String>,
322        owners: Vec<Owner>,
323        repository: Option<String>,
324    ) -> Self {
325        Self {
326            name,
327            version,
328            standard,
329            project_type,
330            repository,
331            owners,
332            description: None,
333            license: None,
334            dependency: None,
335            dev_dependency: None,
336        }
337    }
338    pub fn get_name(&self) -> String{
339        self.name.clone()
340    }
341    pub fn get_version(&self) -> String{
342        self.version.clone()
343    }
344    pub fn get_description(&self) -> Option<String>{
345        match &self.description {
346            Some(desc) => Some(desc.clone()),
347            None => None
348        }
349    }
350}
351impl PartialEq for Package {
352    fn eq(&self, other: &Self) -> bool {
353        if self.version == other.version && self.name == other.name {
354            return true;
355        }
356        false
357    }
358}
359impl Owner {
360    pub fn new(name: String) -> Self {
361        Self {
362            name,
363            email: "unset".to_string(),
364        }
365    }
366}