Commit 1748b51a authored by Andrew Jones's avatar Andrew Jones Committed by Hero Bird

[cli] contract new template (#90)

* WIP: add template

* Zip build file from temp dir

* Unzip in new command

* Unzip files to target dir

* Fix warnings

* Remove unwraps from unzipping

* Refactor build.rs

* rustfmt

* Remove printlns

* Use PathBuf in build.rs

* Replace variables in template files

* Diable zip default features

* rustfmt
parent 110d83a3
......@@ -16,5 +16,8 @@ Cargo.lock
# Ignore VS Code artifacts.
**/.vscode/**
# Ignore idea artifacts.
**/.idea/**
# Ignore history files.
**/.history/**
......@@ -2,6 +2,7 @@
name = "cargo-contract"
version = "0.1.1"
authors = ["Parity Technologies <admin@parity.io>"]
build = "build.rs"
edition = "2018"
license = "GPL-3.0"
......@@ -17,3 +18,8 @@ include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"]
structopt = "0.2.15"
itertools = "0.8"
heck = "0.3"
zip = { version = "0.5", default-features = false }
[build-dependencies]
zip = { version = "0.5", default-features = false }
walkdir = "1.0"
use std::{
error::Error,
io::{
prelude::*,
Write,
},
iter::Iterator,
result::Result,
};
use zip::{
result::ZipError,
write::FileOptions,
CompressionMethod,
ZipWriter,
};
use std::{
fs::File,
path::{
Path,
PathBuf,
},
};
use walkdir::WalkDir;
const DEFAULT_UNIX_PERMISSIONS: u32 = 0o755;
fn main() {
let src_dir = PathBuf::from("./template");
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR should be set by cargo");
let dst_file = Path::new(&out_dir).join("template.zip");
match zip_dir(&src_dir, &dst_file, CompressionMethod::Stored) {
Ok(_) => {
println!(
"done: {} written to {}",
src_dir.display(),
dst_file.display()
)
}
Err(e) => eprintln!("Error: {:?}", e),
};
}
fn zip_dir(
src_dir: &PathBuf,
dst_file: &PathBuf,
method: CompressionMethod,
) -> Result<(), Box<Error>> {
if !src_dir.is_dir() {
return Err(ZipError::FileNotFound.into())
}
let file = File::create(dst_file)?;
let walkdir = WalkDir::new(src_dir);
let it = walkdir.into_iter().filter_map(|e| e.ok());
let mut zip = ZipWriter::new(file);
let options = FileOptions::default()
.compression_method(method)
.unix_permissions(DEFAULT_UNIX_PERMISSIONS);
let mut buffer = Vec::new();
for entry in it {
let path = entry.path();
let name = path.strip_prefix(&src_dir)?;
if path.is_file() {
zip.start_file_from_path(name, options)?;
let mut f = File::open(path)?;
f.read_to_end(&mut buffer)?;
zip.write_all(&*buffer)?;
buffer.clear();
} else if name.as_os_str().len() != 0 {
zip.add_directory_from_path(name, options)?;
}
}
zip.finish()?;
Ok(())
}
......@@ -18,6 +18,7 @@ use std::{
io::Error as IoError,
result::Result as StdResult,
};
use zip::result::ZipError;
/// The kinds of command errors.
#[derive(Debug)]
......@@ -25,6 +26,7 @@ pub enum CommandErrorKind {
Io(IoError),
UnimplementedCommand,
UnimplementedAbstractionLayer,
ZipError(ZipError),
}
/// An error that can be encountered while executing commands.
......@@ -41,6 +43,14 @@ impl From<IoError> for CommandError {
}
}
impl From<ZipError> for CommandError {
fn from(error: ZipError) -> Self {
Self {
kind: CommandErrorKind::ZipError(error),
}
}
}
impl CommandError {
/// Creates a new command error from the given kind.
pub fn new(kind: CommandErrorKind) -> Self {
......
......@@ -22,199 +22,65 @@ use crate::{
},
AbstractionLayer,
};
/// Returns a file path from the given segments.
fn filepath_from_segs<I, S>(structure: I) -> String
where
I: IntoIterator<Item = S>,
S: std::fmt::Display,
{
itertools::join(structure, "/")
}
/// Returns the contents of the `Cargo.toml` file for the given smart contract name.
fn cargo_toml_contents(name: &str) -> String {
format!(
r##"[package]
name = "{}"
version = "0.1.0"
authors = ["[your_name] <[your_email]>"]
edition = "2018"
[dependencies]
ink_core = {{ git = "https://github.com/paritytech/ink", package = "ink_core" }}
ink_model = {{ git = "https://github.com/paritytech/ink", package = "ink_model" }}
ink_lang = {{ git = "https://github.com/paritytech/ink", package = "ink_lang" }}
parity-codec = {{ version = "3.3", default-features = false, features = ["derive"] }}
[lib]
name = "{}"
crate-type = ["cdylib"]
[features]
default = []
test-env = [
"ink_core/test-env",
"ink_model/test-env",
"ink_lang/test-env",
]
generate-api-description = [
"ink_lang/generate-api-description"
]
[profile.release]
panic = "abort"
lto = true
opt-level = "z""##,
name, name
)
}
/// Returns the contents of a generic `.gitignore` file.
fn gitignore_contents() -> String {
r##"# Ignore build artifacts from the local tests sub-crate.
/target/
# Ignore backup files creates by cargo fmt.
**/*.rs.bk
# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock"##
.to_owned()
}
/// Returns the contents of the specific `.cargo/config` file.
fn cargo_config_contents() -> String {
r##"[target.wasm32-unknown-unknown]
rustflags = [
"-C", "overflow-checks=on",
"-C", "link-args=-z stack-size=65536 --import-memory"
]"##
.to_owned()
}
/// Returns the contents of the dummy smart contract.
fn lib_rs_contents(name: &str) -> String {
use heck::CamelCase as _;
let camel_name = name.to_camel_case();
format!(
r##"#![cfg_attr(not(any(test, feature = "std")), no_std)]
use ink_core::{{
env::println,
memory::format,
storage,
}};
use ink_lang::contract;
contract! {{
/// This simple dummy contract has a `bool` value that can
/// alter between `true` and `false` using the `flip` message.
/// Users can retrieve its current state using the `get` message.
struct {} {{
/// The current state of our flag.
value: storage::Value<bool>,
}}
impl Deploy for {} {{
/// Initializes our state to `false` upon deploying our smart contract.
fn deploy(&mut self) {{
self.value.set(false)
}}
}}
impl {} {{
/// Flips the current state of our smart contract.
pub(external) fn flip(&mut self) {{
*self.value = !*self.value;
}}
/// Returns the current state.
pub(external) fn get(&self) -> bool {{
println(&format!("Storage Value: {{:?}}", *self.value));
*self.value
}}
}}
}}
#[cfg(all(test, feature = "test-env"))]
mod tests {{
use super::*;
#[test]
fn it_works() {{
let mut contract = {}::deploy_mock();
assert_eq!(contract.get(), false);
contract.flip();
assert_eq!(contract.get(), true);
}}
}}
"##,
camel_name, camel_name, camel_name, camel_name,
)
}
/// Returns the contents of the `build.sh` file.
///
/// # Note
///
/// The `build.sh` file is only a temporary solution until we
/// support the same functionality within `pdsl_cli`.
fn build_sh_contents(name: &str) -> String {
format!(r##"#!/bin/bash
set -e
PROJNAME={}
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo build --release --features generate-api-description --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-opt -Oz target/$PROJNAME.wasm -o target/$PROJNAME-opt.wasm
wasm-prune --exports call,deploy target/$PROJNAME-opt.wasm target/$PROJNAME-pruned.wasm"##,
name
)
}
fn rust_toolchain_contents() -> String {
r##"nightly-2019-04-20"##.to_owned()
}
use heck::CamelCase as _;
use std::{
fs,
io::{
Cursor,
Seek,
SeekFrom,
Write,
},
path,
};
use std::io::Read;
/// Initializes a project structure for the `lang` abstraction layer.
fn initialize_for_lang(name: &str) -> Result<()> {
use std::fs;
fs::create_dir(name)?;
fs::create_dir(filepath_from_segs(&[name, ".cargo"]))?;
fs::create_dir(filepath_from_segs(&[name, "src"]))?;
fs::write(
filepath_from_segs(&[name, ".cargo", "config"]),
cargo_config_contents(),
)?;
fs::write(
filepath_from_segs(&[name, "Cargo.toml"]),
cargo_toml_contents(name),
)?;
fs::write(
filepath_from_segs(&[name, ".gitignore"]),
gitignore_contents(),
)?;
fs::write(
filepath_from_segs(&[name, "src", "lib.rs"]),
lib_rs_contents(name),
)?;
fs::write(
filepath_from_segs(&[name, "build.sh"]),
build_sh_contents(name),
)?;
fs::write(
filepath_from_segs(&[name, "rust-toolchain"]),
rust_toolchain_contents(),
)?;
let out_dir = path::Path::new(name);
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)?;
}
}
let mut outfile = fs::File::create(&outpath)?;
outfile.write(contents.as_bytes())?;
}
// 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))?;
}
}
}
Ok(())
}
......
[target.wasm32-unknown-unknown]
rustflags = [
"-C", "overflow-checks=on",
"-C", "link-args=-z stack-size=65536 --import-memory"
]
\ No newline at end of file
# Ignore build artifacts from the local tests sub-crate.
/target/
# Ignore backup files creates by cargo fmt.
**/*.rs.bk
# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
\ No newline at end of file
[package]
name = "{{name}}"
version = "0.1.0"
authors = ["[your_name] <[your_email]>"]
edition = "2018"
[dependencies]
ink_core = { git = "https://github.com/paritytech/ink", package = "ink_core" }
ink_model = { git = "https://github.com/paritytech/ink", package = "ink_model" }
ink_lang = { git = "https://github.com/paritytech/ink", package = "ink_lang" }
parity-codec = { version = "3.3", default-features = false, features = ["derive"] }
[lib]
name = "{{name}}"
crate-type = ["cdylib"]
[features]
default = []
test-env = [
"ink_core/test-env",
"ink_model/test-env",
"ink_lang/test-env",
]
generate-api-description = [
"ink_lang/generate-api-description"
]
[profile.release]
panic = "abort"
lto = true
opt-level = "z"
#!/bin/bash
set -e
PROJNAME={{name}}
# cargo clean
# rm Cargo.lock
CARGO_INCREMENTAL=0 &&
cargo build --release --features generate-api-description --target=wasm32-unknown-unknown --verbose
wasm2wat -o target/$PROJNAME.wat target/wasm32-unknown-unknown/release/$PROJNAME.wasm
cat target/$PROJNAME.wat | sed "s/(import \"env\" \"memory\" (memory (;0;) 2))/(import \"env\" \"memory\" (memory (;0;) 2 16))/" > target/$PROJNAME-fixed.wat
wat2wasm -o target/$PROJNAME.wasm target/$PROJNAME-fixed.wat
wasm-opt -Oz target/$PROJNAME.wasm -o target/$PROJNAME-opt.wasm
wasm-prune --exports call,deploy target/$PROJNAME-opt.wasm target/$PROJNAME-pruned.wasm
nightly-2019-04-20
\ No newline at end of file
#![cfg_attr(not(any(test, feature = "std")), no_std)]
use ink_core::{
env::println,
memory::format,
storage,
};
use ink_lang::contract;
contract! {
/// This simple dummy contract has a `bool` value that can
/// alter between `true` and `false` using the `flip` message.
/// Users can retrieve its current state using the `get` message.
struct {{camel_name}} {
/// The current state of our flag.
value: storage::Value<bool>,
}
impl Deploy for {{camel_name}} {
/// Initializes our state to `false` upon deploying our smart contract.
fn deploy(&mut self) {
self.value.set(false)
}
}
impl {{camel_name}} {
/// Flips the current state of our smart contract.
pub(external) fn flip(&mut self) {
*self.value = !*self.value;
}
/// Returns the current state.
pub(external) fn get(&self) -> bool {
println(&format!("Storage Value: {:?}", *self.value));
*self.value
}
}
}
#[cfg(all(test, feature = "test-env"))]
mod tests {
use super::*;
#[test]
fn it_works() {
let mut contract = {{camel_name}}::deploy_mock();
assert_eq!(contract.get(), false);
contract.flip();
assert_eq!(contract.get(), true);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment