1pub mod snippets;
4
5use crate::codegen::snippets::{
6 CARGO_TOML_PLAIN, CARGO_TOML_PLAIN_V4, CARGO_TOML_PLAIN_V5, CARGO_TOML_SNIPPET,
7 CARGO_TOML_SNIPPET_V4, CARGO_TOML_SNIPPET_V5, CONTRACT_PLAIN, CONTRACT_PLAIN_V4,
8 CONTRACT_PLAIN_V5, CONTRACT_SNIPPET, CONTRACT_SNIPPET_V4, CONTRACT_SNIPPET_V5,
9};
10use crate::{utils, Version};
11
12#[derive(Debug, PartialEq, Eq)]
15pub struct Project {
16 pub lib: ProjectFile,
18 pub cargo: ProjectFile,
20}
21
22#[derive(Debug, PartialEq, Eq)]
25pub struct ProjectFile {
26 pub plain: String,
28 pub snippet: Option<String>,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum Error {
36 PackageName,
40 ContractName,
44}
45
46pub fn new_project(name: String, version: Version) -> Result<Project, Error> {
48 if name.is_empty()
51 || !name
52 .chars()
53 .all(|c| c.is_alphanumeric() || c == '_' || c == '-')
54 {
55 return Err(Error::PackageName);
56 }
57
58 if !name.chars().next().is_some_and(char::is_alphabetic) {
61 return Err(Error::ContractName);
62 }
63
64 let module_name = name.replace('-', "_");
66 let struct_name = utils::pascal_case(&module_name);
67
68 Ok(Project {
70 lib: ProjectFile {
72 plain: if version.is_legacy() {
73 CONTRACT_PLAIN_V4
74 } else if version.is_v5() {
75 CONTRACT_PLAIN_V5
76 } else {
77 CONTRACT_PLAIN
78 }
79 .replace("my_contract", &module_name)
80 .replace("MyContract", &struct_name),
81 snippet: Some(
82 if version.is_legacy() {
83 CONTRACT_SNIPPET_V4
84 } else if version.is_v5() {
85 CONTRACT_SNIPPET_V5
86 } else {
87 CONTRACT_SNIPPET
88 }
89 .replace("my_contract", &module_name)
90 .replace("MyContract", &struct_name),
91 ),
92 },
93 cargo: ProjectFile {
95 plain: if version.is_legacy() {
96 CARGO_TOML_PLAIN_V4
97 } else if version.is_v5() {
98 CARGO_TOML_PLAIN_V5
99 } else {
100 CARGO_TOML_PLAIN
101 }
102 .replace("my_contract", &name),
103 snippet: Some(
104 if version.is_legacy() {
105 CARGO_TOML_SNIPPET_V4
106 } else if version.is_v5() {
107 CARGO_TOML_SNIPPET_V5
108 } else {
109 CARGO_TOML_SNIPPET
110 }
111 .replace("my_contract", &name),
112 ),
113 },
114 })
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::{Analysis, MinorVersion};
121
122 #[test]
125 fn invalid_project_name_fails() {
126 for (name, expected_error) in [
127 ("", Error::PackageName),
129 ("hello!", Error::PackageName),
131 ("hello world", Error::PackageName),
132 ("💝", Error::PackageName),
133 ("1hello", Error::ContractName),
135 ("-hello", Error::ContractName),
136 ("_hello", Error::ContractName),
137 ] {
138 assert_eq!(
139 new_project(name.to_owned(), Version::Legacy),
140 Err(expected_error)
141 );
142 }
143 }
144
145 #[test]
146 fn valid_project_name_works() {
147 for name in ["hello", "hello_world", "hello-world"] {
148 let result = new_project(name.to_owned(), Version::Legacy);
150 assert!(result.is_ok());
151
152 let contract_code = result.unwrap().lib.plain;
154 let analysis = Analysis::new(&contract_code, Version::Legacy);
155 assert_eq!(analysis.diagnostics().len(), 0);
156 }
157 }
158
159 #[test]
160 fn new_project_works() {
161 for version in [
162 Version::Legacy,
163 Version::V5(MinorVersion::Latest),
164 Version::V6,
165 ] {
166 let result = new_project("hello_world".to_owned(), version);
168 assert!(result.is_ok());
169
170 let project = result.unwrap();
172 let cargo_toml = project.cargo.plain;
173 assert!(cargo_toml.contains(if version.is_legacy() {
174 r#"ink = { version = "4"#
175 } else if version.is_v5() {
176 r#"ink = { version = "5"#
177 } else {
178 r#"version = "6"#
179 }));
180 let contract_code = project.lib.plain;
181 let analysis = Analysis::new(&contract_code, version);
182 assert_eq!(analysis.diagnostics().len(), 0);
183 }
184 }
185}