1use crate::manifest::{self, Manifest, ManifestFile};
4use std::{
5 fs,
6 io::{self, Write},
7 path::{Path, PathBuf},
8};
9use thiserror::Error;
10
11#[derive(Debug, Default)]
13pub struct Options {
14 pub name: Option<String>,
18 pub kind: Option<manifest::PackageKind>,
22}
23
24#[derive(Debug, Error)]
25pub enum NewPkgError {
26 #[error("the path {0:?} already exists and is not a directory")]
28 ExistingPathNotDirectory(PathBuf),
29 #[error("an I/O error occurred: {0}")]
31 Io(#[from] io::Error),
32 #[error("the given directory already contains a pint manifest: {0:?}")]
34 ManifestExists(PathBuf),
35 #[error("failed to retrieve package name from given path: {0:?}")]
37 NameFromPath(PathBuf),
38 #[error("package name {0:?} is invalid: {1}")]
40 InvalidPkgName(String, manifest::InvalidName),
41}
42
43pub fn new_pkg(path: &Path, opts: Options) -> Result<PathBuf, NewPkgError> {
49 let manifest_path = path.join(ManifestFile::FILE_NAME);
50 let src_path = path.join("src");
51
52 if path.exists() {
54 if !path.is_dir() {
55 return Err(NewPkgError::ExistingPathNotDirectory(path.to_path_buf()));
56 }
57 if manifest_path.exists() {
58 return Err(NewPkgError::ManifestExists(manifest_path));
59 }
60 } else {
61 fs::create_dir_all(path)?;
62 }
63
64 let path = path.canonicalize()?;
66
67 let kind = opts.kind.unwrap_or_default();
69 let name = match opts.name {
70 Some(name) => name,
71 None => path
72 .file_stem()
73 .ok_or_else(|| NewPkgError::NameFromPath(path.to_path_buf()))?
74 .to_string_lossy()
75 .to_string(),
76 };
77
78 manifest::check_name(&name).map_err(|e| NewPkgError::InvalidPkgName(name.to_string(), e))?;
80
81 fs::create_dir_all(&src_path)?;
83
84 let manifest_string = new_manifest_string(&name, &kind);
86 fs::write(&manifest_path, &manifest_string)?;
87
88 let manifest: Manifest = manifest_string.parse().expect("checked in unit testing");
90 let pnt_path = src_path.join(manifest.entry_point_str());
91 if !pnt_path.exists() {
92 let pnt_string = default_pnt_str(&kind);
93 fs::write(pnt_path, pnt_string)?;
94 }
95
96 let gitignore_path = path.join(".gitignore");
98 let mut gitignore_file = fs::OpenOptions::new()
99 .append(true)
100 .create(true)
101 .open(gitignore_path)?;
102 gitignore_file.write_all(GITIGNORE.as_bytes())?;
103
104 Ok(manifest_path)
105}
106
107fn default_pnt_str(kind: &manifest::PackageKind) -> &'static str {
108 match kind {
109 manifest::PackageKind::Contract => DEFAULT_CONTRACT_PNT,
110 manifest::PackageKind::Library => DEFAULT_LIBRARY_PNT,
111 }
112}
113
114fn new_manifest_string(name: &str, kind: &manifest::PackageKind) -> String {
115 format!(
116 r#"[package]
117name = "{name}"
118kind = "{kind}"
119
120[dependencies]
121# Library dependencies go here.
122
123[contract-dependencies]
124# Contract dependencies go here.
125"#
126 )
127}
128
129const DEFAULT_CONTRACT_PNT: &str = r#"storage {
130 counter: int,
131}
132
133predicate Increment() {
134 let counter: int? = storage::counter;
135 constraint storage::counter := (counter == nil) ? 1 : counter! + 1;
136}
137"#;
138
139const DEFAULT_LIBRARY_PNT: &str = r#"union Animal = Cat | Dog;
140
141type Person = {
142 address: b256,
143 pet: Animal,
144};
145"#;
146
147const GITIGNORE: &str = r#"out"#;