Skip to main content

wini_cli/init/
ask.rs

1use {
2    super::{err::InitError, git::clone, sep, RepoSummary},
3    crate::{
4        init::{
5            git::use_branch,
6            input,
7            rename::rename_fields,
8            select,
9            HEADER,
10            OFFICIAL_REPOSITORY_BRANCHES,
11            OFFICIAL_REPOSITORY_OPTIONS,
12            RENDER_CONFIG,
13            WINI_REPO,
14        },
15        utils::{copy_dir_all, generate_random_string},
16    },
17    git2::{BranchType, Repository},
18    inquire::set_global_render_config,
19    std::{fs, path::Path},
20};
21
22
23
24/// Creates the repository project from one of the official template of wini
25pub fn from_official_repository() -> Result<RepoSummary, InitError> {
26    let handle_clone_official_repository = std::thread::spawn(|| clone(WINI_REPO));
27
28    sep();
29
30    let result = (|| {
31        let branch_index = select(
32            "Which template should be used",
33            OFFICIAL_REPOSITORY_OPTIONS.to_vec(),
34        )?;
35        let branch = OFFICIAL_REPOSITORY_BRANCHES[branch_index].to_owned();
36
37        sep();
38
39        let project_name = input("Project name:")?;
40
41        let path = Path::new(&project_name);
42        if path.exists() && path.is_dir() {
43            return Err(InitError::AlreadyExists(project_name));
44        }
45
46        Ok((project_name, branch))
47    })();
48
49    // We force the creation of the repository because if everything went right, we will need to
50    // rename it.
51    // Else, if there was an error, we will need to delete it. In both case, we want it to be
52    // created.
53    let current_repository_name = handle_clone_official_repository.join().unwrap()?;
54
55    match result {
56        Ok((project_name, branch)) => {
57            // At this point the repository is created.
58            // If, for some reason, it fails on the following closure, we will delete it.
59            match (|| {
60                fs::rename(&current_repository_name, &project_name).map_err(InitError::IoError)?;
61
62                let last_commit_hash =
63                    use_branch(&project_name, &branch).map_err(InitError::OtherGitError)?;
64
65                Ok(RepoSummary {
66                    dir: project_name,
67                    branch,
68                    last_commit_hash,
69                    remote_url: Some(WINI_REPO.to_string()),
70                })
71            })() {
72                Ok(summary) => Ok(summary),
73                Err(err) => {
74                    std::fs::remove_dir_all(current_repository_name).map_err(InitError::IoError)?;
75                    Err(err)
76                },
77            }
78        },
79        Err(err) => {
80            std::fs::remove_dir_all(current_repository_name).map_err(InitError::IoError)?;
81            Err(err)
82        },
83    }
84}
85
86
87
88/// Ask informations about the branch and the project name, and proceed to setup the repository
89/// correctly.
90pub fn handle_project_setup_for_custom(
91    current_repository_name: &str,
92    remote_url: Option<String>,
93) -> Result<RepoSummary, InitError> {
94    let branches = {
95        let repo = Repository::open(current_repository_name).map_err(InitError::OtherGitError)?;
96        let branches = repo
97            .branches(Some(BranchType::Remote))
98            .map_err(|_| InitError::OtherGitError(git2::Error::from_str("No branch found.")))?;
99
100        branches
101            .filter_map(|e| {
102                e.ok().and_then(|(b, _)| {
103                    b.name()
104                        .ok()
105                        .flatten()
106                        .map(|name| name.replace("origin/", ""))
107                })
108            })
109            .filter(|s| s != "HEAD")
110            .collect::<Vec<String>>()
111    };
112    let branch_index = select("Which branch should be used ?", branches.clone())?;
113    let branch = &branches[branch_index];
114
115    sep();
116
117    let project_name = input("Project name:")?;
118
119    let path = Path::new(&project_name);
120    if path.exists() && path.is_dir() {
121        return Err(InitError::AlreadyExists(project_name));
122    }
123
124    fs::rename(current_repository_name, &project_name).map_err(InitError::IoError)?;
125
126    let last_commit_hash = use_branch(&project_name, branch).map_err(InitError::OtherGitError)?;
127
128    Ok(RepoSummary {
129        dir: project_name,
130        branch: branch.to_owned(),
131        last_commit_hash,
132        remote_url,
133    })
134}
135
136
137/// Creates the repository project from a remote repository
138pub fn from_custom_remote_repository() -> Result<RepoSummary, InitError> {
139    let remote_url = input("Remote repository URL:")?;
140
141    let current_repository_name = match clone(&remote_url) {
142        Ok(n) => n,
143        Err(InitError::OtherGitError(git_error)) => {
144            if git_error.code() == git2::ErrorCode::NotFound ||
145                git_error.class() == git2::ErrorClass::Http
146            {
147                eprintln!("{}", InitError::CouldntCloneRepo(remote_url));
148                sep();
149
150                return from_custom_remote_repository();
151            } else {
152                return Err(InitError::OtherGitError(git_error));
153            }
154        },
155        Err(fail) => return Err(fail),
156    };
157
158    match handle_project_setup_for_custom(&current_repository_name, Some(remote_url)) {
159        Ok(sum) => Ok(sum),
160        Err(err) => {
161            std::fs::remove_dir_all(current_repository_name).map_err(InitError::IoError)?;
162            Err(err)
163        },
164    }
165}
166
167
168
169/// Creates the repository project from a local repository (probably not a good thing)
170pub fn from_cutom_local_repository() -> Result<RepoSummary, InitError> {
171    // The repository path to copy from
172    let repository_path = {
173        let mut repository_path: Option<String> = None;
174
175        while repository_path.is_none() {
176            let input_repository_path = input("Local repository path:")?;
177
178            let repository_path_struct = Path::new(&input_repository_path);
179
180            if repository_path_struct.exists() {
181                let path_of_git_dir_string = format!("{input_repository_path}/.git");
182                let path_of_git_dir = Path::new(&path_of_git_dir_string);
183
184                if path_of_git_dir.exists() && path_of_git_dir.is_dir() {
185                    repository_path = Some(input_repository_path);
186                } else {
187                    eprintln!(
188                        "{}",
189                        InitError::PathExistsButIsNotGit(input_repository_path)
190                    )
191                }
192            } else {
193                eprintln!("{}", InitError::InvalidPath(input_repository_path))
194            }
195        }
196
197        repository_path.expect("Can't be None.")
198    };
199
200    let current_repository_name = generate_random_string(64);
201    copy_dir_all(repository_path, &current_repository_name).map_err(InitError::IoError)?;
202
203
204    match handle_project_setup_for_custom(&current_repository_name, None) {
205        Ok(sum) => Ok(sum),
206        Err(err) => {
207            std::fs::remove_dir_all(current_repository_name).map_err(InitError::IoError)?;
208            Err(err)
209        },
210    }
211}
212
213
214/// Ask the user how they want to create the project
215pub fn ask() -> Result<(), InitError> {
216    set_global_render_config(*RENDER_CONFIG);
217
218    println!("{HEADER}");
219
220    sep();
221
222    let selection = select(
223        "Create a project from",
224        vec![
225            "Official wini templates",
226            "Remote git repository",
227            "Local git repository",
228        ],
229    )?;
230
231    sep();
232
233    let repo_summary = match selection {
234        0 => from_official_repository()?,
235        1 => from_custom_remote_repository()?,
236        2 => from_cutom_local_repository()?,
237        _ => unreachable!(),
238    };
239
240    rename_fields(&repo_summary)?;
241
242    sep();
243    println!(
244        "\x1B[32m◆\x1B[0m Project created at `\x1B[32;1m./{}\x1b[0m`!",
245        repo_summary.dir
246    );
247
248    Ok(())
249}