pint_pkg/
new.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Items related to the creation of new packages.

use crate::manifest::{self, Manifest, ManifestFile};
use std::{
    fs,
    io::{self, Write},
    path::{Path, PathBuf},
};
use thiserror::Error;

/// Options for the `new_pkg` function.
#[derive(Debug, Default)]
pub struct Options {
    /// A name for the package.
    ///
    /// If `None`, the name of the directory is used.
    pub name: Option<String>,
    /// Optionally specify a package kind.
    ///
    /// If `None`, `Contract` is used.
    pub kind: Option<manifest::PackageKind>,
}

#[derive(Debug, Error)]
pub enum NewPkgError {
    /// The given path already exists and is not a directory.
    #[error("the path {0:?} already exists and is not a directory")]
    ExistingPathNotDirectory(PathBuf),
    /// An I/O error occurred.
    #[error("an I/O error occurred: {0}")]
    Io(#[from] io::Error),
    /// The given directory already contains a pint manifest.
    #[error("the given directory already contains a pint manifest: {0:?}")]
    ManifestExists(PathBuf),
    /// Failed to retrieve a package name from the given path.
    #[error("failed to retrieve package name from given path: {0:?}")]
    NameFromPath(PathBuf),
    /// The package name is invalid.
    #[error("package name {0:?} is invalid: {1}")]
    InvalidPkgName(String, manifest::InvalidName),
}

/// Create a new package at the given path.
///
/// If the directory does not yet exist, it will be created.
///
/// On success, returns the path to the package's manifest.
pub fn new_pkg(path: &Path, opts: Options) -> Result<PathBuf, NewPkgError> {
    let manifest_path = path.join(ManifestFile::FILE_NAME);
    let src_path = path.join("src");

    // Check that we wouldn't overwrite something, and that the dir exists.
    if path.exists() {
        if !path.is_dir() {
            return Err(NewPkgError::ExistingPathNotDirectory(path.to_path_buf()));
        }
        if manifest_path.exists() {
            return Err(NewPkgError::ManifestExists(manifest_path));
        }
    } else {
        fs::create_dir_all(path)?;
    }

    // Now that we know the dir exists, we can canonicalise the path.
    let path = path.canonicalize()?;

    // Determine the kind and pkg name.
    let kind = opts.kind.unwrap_or_default();
    let name = match opts.name {
        Some(name) => name,
        None => path
            .file_stem()
            .ok_or_else(|| NewPkgError::NameFromPath(path.to_path_buf()))?
            .to_string_lossy()
            .to_string(),
    };

    // Validate the pkg name.
    manifest::check_name(&name).map_err(|e| NewPkgError::InvalidPkgName(name.to_string(), e))?;

    // Create the `src` dir.
    fs::create_dir_all(&src_path)?;

    // Create the manifest file.
    let manifest_string = new_manifest_string(&name, &kind);
    fs::write(&manifest_path, &manifest_string)?;

    // Create the default pint file.
    let manifest: Manifest = manifest_string.parse().expect("checked in unit testing");
    let pnt_path = src_path.join(manifest.entry_point_str());
    if !pnt_path.exists() {
        let pnt_string = default_pnt_str(&kind);
        fs::write(pnt_path, pnt_string)?;
    }

    // Create or append to .gitignore file.
    let gitignore_path = path.join(".gitignore");
    let mut gitignore_file = fs::OpenOptions::new()
        .append(true)
        .create(true)
        .open(gitignore_path)?;
    gitignore_file.write_all(GITIGNORE.as_bytes())?;

    Ok(manifest_path)
}

fn default_pnt_str(kind: &manifest::PackageKind) -> &'static str {
    match kind {
        manifest::PackageKind::Contract => DEFAULT_CONTRACT_PNT,
        manifest::PackageKind::Library => DEFAULT_LIBRARY_PNT,
    }
}

fn new_manifest_string(name: &str, kind: &manifest::PackageKind) -> String {
    format!(
        r#"[package]
name = "{name}"
kind = "{kind}"

[dependencies]
# Library dependencies go here.

[contract-dependencies]
# Contract dependencies go here.
"#
    )
}

const DEFAULT_CONTRACT_PNT: &str = r#"storage {
    counter: int,
}

predicate Increment {
    state counter: int = mut storage::counter;
    constraint (counter == nil && counter' == 1) || counter' == counter + 1;
}
"#;

const DEFAULT_LIBRARY_PNT: &str = r#"union Animal = Cat | Dog;

type Person = {
    address: b256,
    pet: Animal,
};
"#;

const GITIGNORE: &str = r#"out"#;