new.rs 4.91 KB
Newer Older
1
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
2
// This file is part of ink!.
3
//
4
// ink! is free software: you can redistribute it and/or modify
5 6 7 8
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
9
// ink! is distributed in the hope that it will be useful,
10 11 12 13 14
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
15
// along with ink!.  If not, see <http://www.gnu.org/licenses/>.
16

17 18 19 20 21 22 23
use crate::{
    cmd::{
        CommandError,
        Result,
    },
    AbstractionLayer,
};
24 25 26 27 28
use heck::CamelCase as _;
use std::{
    fs,
    io::{
        Cursor,
Hero Bird's avatar
Hero Bird committed
29
        Read,
30 31 32 33 34 35
        Seek,
        SeekFrom,
        Write,
    },
    path,
};
36

37
/// Initializes a project structure for the `lang` abstraction layer.
38
fn initialize_for_lang(name: &str) -> Result<String> {
39 40 41
    if name.contains("-") {
        return Err("Contract names cannot contain hyphens".into())
    }
42

43
    let out_dir = path::Path::new(name);
44 45 46 47 48 49
    if out_dir.join("Cargo.toml").exists() {
        return Err(format!("A Cargo package already exists in {}", name).into())
    }
    if !out_dir.exists() {
        fs::create_dir(out_dir)?;
    }
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

    let template = include_bytes!(concat!(env!("OUT_DIR"), "/template.zip"));
    let mut cursor = Cursor::new(Vec::new());
    cursor.write_all(template)?;
    cursor.seek(SeekFrom::Start(0))?;

    let mut archive = zip::ZipArchive::new(cursor)?;

    for i in 0..archive.len() {
        let mut file = archive.by_index(i)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;

        // replace template placeholders
        let contents = contents.replace("{{name}}", name);
        let contents = contents.replace("{{camel_name}}", &name.to_camel_case());

        let outpath = out_dir.join(file.sanitized_name());

        if (&*file.name()).ends_with('/') {
            fs::create_dir_all(&outpath)?;
        } else {
            if let Some(p) = outpath.parent() {
                if !p.exists() {
                    fs::create_dir_all(&p)?;
                }
            }
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
            let mut outfile = fs::OpenOptions::new()
                .write(true)
                .create_new(true)
                .open(outpath.clone())
                .map_err(|e| {
                    if e.kind() == std::io::ErrorKind::AlreadyExists {
                        CommandError::from(format!(
                            "New contract file {} already exists",
                            outpath.display()
                        ))
                    } else {
                        CommandError::from(e)
                    }
                })?;

Hero Bird's avatar
Hero Bird committed
92
            outfile.write_all(contents.as_bytes())?;
93 94 95 96 97 98 99 100 101 102 103 104 105
        }

        // Get and set permissions
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;

            if let Some(mode) = file.unix_mode() {
                fs::set_permissions(&outpath, fs::Permissions::from_mode(mode))?;
            }
        }
    }

106
    Ok(format!("Created contract {}", name))
107 108
}

109
pub(crate) fn execute_new(layer: AbstractionLayer, name: &str) -> Result<String> {
110
    match layer {
Andrew Jones's avatar
Andrew Jones committed
111 112
        AbstractionLayer::Core => Err(CommandError::UnimplementedAbstractionLayer),
        AbstractionLayer::Model => Err(CommandError::UnimplementedAbstractionLayer),
113 114 115
        AbstractionLayer::Lang => initialize_for_lang(name),
    }
}
116 117 118

#[cfg(test)]
mod tests {
119 120
    use super::*;

121 122 123 124 125
    #[test]
    fn rejects_hyphenated_name() {
        let result = super::initialize_for_lang("should-fail");
        assert_eq!(
            format!("{:?}", result),
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
            r#"Err(Other("Contract names cannot contain hyphens"))"#
        )
    }

    #[test]
    fn contract_cargo_project_already_exists() {
        let name = "test_contract_cargo_project_already_exists";
        let _ = super::initialize_for_lang(name);
        let result = super::initialize_for_lang(name);
        // clean up created files
        std::fs::remove_dir_all(name).unwrap();
        assert_eq!(
            format!("{:?}", result),
            r#"Err(Other("A Cargo package already exists in test_contract_cargo_project_already_exists"))"#
        )
    }

    #[test]
    fn dont_overwrite_existing_files_not_in_cargo_project() {
        let name = "dont_overwrite_existing_files";
        let dir = path::Path::new(name);
        fs::create_dir_all(dir).unwrap();
148
        fs::File::create(dir.join(".gitignore")).unwrap();
149 150 151 152 153
        let result = super::initialize_for_lang(name);
        // clean up created files
        std::fs::remove_dir_all(dir).unwrap();
        assert_eq!(
            format!("{:?}", result),
154
            r#"Err(Other("New contract file dont_overwrite_existing_files/.gitignore already exists"))"#
155 156 157
        )
    }
}