pint_pkg/
new.rs

1//! Items related to the creation of new packages.
2
3use crate::manifest::{self, Manifest, ManifestFile};
4use std::{
5    fs,
6    io::{self, Write},
7    path::{Path, PathBuf},
8};
9use thiserror::Error;
10
11/// Options for the `new_pkg` function.
12#[derive(Debug, Default)]
13pub struct Options {
14    /// A name for the package.
15    ///
16    /// If `None`, the name of the directory is used.
17    pub name: Option<String>,
18    /// Optionally specify a package kind.
19    ///
20    /// If `None`, `Contract` is used.
21    pub kind: Option<manifest::PackageKind>,
22}
23
24#[derive(Debug, Error)]
25pub enum NewPkgError {
26    /// The given path already exists and is not a directory.
27    #[error("the path {0:?} already exists and is not a directory")]
28    ExistingPathNotDirectory(PathBuf),
29    /// An I/O error occurred.
30    #[error("an I/O error occurred: {0}")]
31    Io(#[from] io::Error),
32    /// The given directory already contains a pint manifest.
33    #[error("the given directory already contains a pint manifest: {0:?}")]
34    ManifestExists(PathBuf),
35    /// Failed to retrieve a package name from the given path.
36    #[error("failed to retrieve package name from given path: {0:?}")]
37    NameFromPath(PathBuf),
38    /// The package name is invalid.
39    #[error("package name {0:?} is invalid: {1}")]
40    InvalidPkgName(String, manifest::InvalidName),
41}
42
43/// Create a new package at the given path.
44///
45/// If the directory does not yet exist, it will be created.
46///
47/// On success, returns the path to the package's manifest.
48pub 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    // Check that we wouldn't overwrite something, and that the dir exists.
53    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    // Now that we know the dir exists, we can canonicalise the path.
65    let path = path.canonicalize()?;
66
67    // Determine the kind and pkg name.
68    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    // Validate the pkg name.
79    manifest::check_name(&name).map_err(|e| NewPkgError::InvalidPkgName(name.to_string(), e))?;
80
81    // Create the `src` dir.
82    fs::create_dir_all(&src_path)?;
83
84    // Create the manifest file.
85    let manifest_string = new_manifest_string(&name, &kind);
86    fs::write(&manifest_path, &manifest_string)?;
87
88    // Create the default pint file.
89    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    // Create or append to .gitignore file.
97    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"#;