1use anyhow::anyhow;
2use std::fs;
3use std::fs::File;
4use std::io::Cursor;
5use std::path::PathBuf;
6use std::str::FromStr;
7use thiserror::Error;
8use zip::ZipArchive;
9
10#[derive(Error, Debug)]
11pub enum CreateError {
12 #[error("filesystem error: {0}")]
13 IoError(anyhow::Error, std::io::Error),
14 #[error("unknown template: {0}")]
15 UnknownTemplate(String),
16 #[error("Connection error: {0}")]
17 ConnectionError(#[from] reqwest::Error),
18 #[error("Zip error: {0}")]
19 ZipError(#[from] zip::result::ZipError),
20 #[error("A directory with this name already exists: {0}")]
21 DirectoryAlreadyExists(String),
22}
23
24#[derive(Debug, Default)]
25pub enum Template {
26 #[default]
27 LocalDefault,
28 Default,
29 Ft,
30 Nft,
31}
32
33impl FromStr for Template {
34 type Err = CreateError;
35
36 fn from_str(s: &str) -> Result<Self, Self::Err> {
37 match s {
38 "local_default" => Ok(Template::LocalDefault),
39 "default" => Ok(Template::Default),
40 "ft" => Ok(Template::Ft),
41 "nft" => Ok(Template::Nft),
42 _ => Err(CreateError::UnknownTemplate(s.to_string())),
43 }
44 }
45}
46
47impl Template {
48 fn get_zip_template(&self) -> Result<ZipArchive<Cursor<Vec<u8>>>, CreateError> {
49 match self {
50 Template::LocalDefault => {
51 let content =
52 include_bytes!(concat!(env!("OUT_DIR"), "/default_template.zip")).to_vec();
53 let reader = std::io::Cursor::new(content);
54 let zip = ZipArchive::new(reader)?;
55 Ok(zip)
56 }
57 Template::Default => {
58 let response_body = reqwest::blocking::get("https://github.com/L1X-Foundation/cargo-l1x-templates/archive/refs/heads/default.zip")?.bytes()?;
59 let reader = Cursor::new(response_body.to_vec());
60 let zip = ZipArchive::new(reader)?;
61 Ok(zip)
62 }
63 Template::Ft => {
64 let response_body = reqwest::blocking::get("https://github.com/L1X-Foundation/cargo-l1x-templates/archive/refs/heads/ft.zip")?.bytes()?;
65 let reader = Cursor::new(response_body.to_vec());
66 let zip = ZipArchive::new(reader)?;
67 Ok(zip)
68 }
69 Template::Nft => {
70 let response_body = reqwest::blocking::get("https://github.com/L1X-Foundation/cargo-l1x-templates/archive/refs/heads/nft.zip")?.bytes()?;
71 let reader = Cursor::new(response_body.to_vec());
72 let zip = ZipArchive::new(reader)?;
73 Ok(zip)
74 }
75 }
76 }
77
78 fn unzip(
79 archive: &mut ZipArchive<Cursor<Vec<u8>>>,
80 destination_path: &PathBuf,
81 ) -> Result<(), CreateError> {
82 let mut top_level_dir_name = None;
83
84 for i in 0..archive.len() {
85 let mut file = archive.by_index(i)?;
86 let mut file_path = file.mangled_name();
87 if let Some(top_level_dir_name) = top_level_dir_name.as_ref() {
88 if let Ok(stripped_file_path) = file_path.strip_prefix(top_level_dir_name) {
89 file_path = stripped_file_path.to_owned();
90 }
91 }
92 let mut path = destination_path.join(file_path);
93 let file_name = file.name().to_owned();
94 if file.is_dir() {
95 if top_level_dir_name.is_none() {
96 top_level_dir_name = Some(file_name);
97 continue; }
99 std::fs::create_dir_all(&path).map_err(|e| {
100 CreateError::IoError(
101 anyhow!("Couldn't create a directory: {}", path.display()),
102 e,
103 )
104 })?;
105 } else {
106 let parent = path.parent().unwrap();
107 if !parent.exists() {
108 std::fs::create_dir_all(parent).map_err(|e| {
109 CreateError::IoError(
110 anyhow!("Couldn't create a directory: {}", parent.display()),
111 e,
112 )
113 })?;
114 }
115 if path.file_name() == Some("Cargo.toml.template".as_ref()) {
116 path = path.with_file_name("Cargo.toml");
117 }
118 let mut outfile = File::create(&path).map_err(|e| {
119 CreateError::IoError(anyhow!("Couldn't create a file: {}", path.display()), e)
120 })?;
121 std::io::copy(&mut file, &mut outfile).map_err(|e| {
122 CreateError::IoError(anyhow!("Couldn't copy file: {}", path.display()), e)
123 })?;
124 }
125 }
126 Ok(())
127 }
128}
129
130pub fn create(name: String, from_template: String) -> Result<(), CreateError> {
131 let template = Template::from_str(&from_template)?;
132
133 let destination_path = PathBuf::from(&name);
134 if destination_path.exists() {
135 return Err(CreateError::DirectoryAlreadyExists(name));
136 }
137
138 fs::create_dir_all(name.clone())
139 .map_err(|e| CreateError::IoError(anyhow!("Couldn't create a directory: {}", name), e))?;
140
141 let mut archive = template.get_zip_template()?;
142
143 Template::unzip(&mut archive, &destination_path)?;
144
145 Ok(())
146}