Skip to content

Commit

Permalink
[cli] Add initial very rough implementation of the cli tool
Browse files Browse the repository at this point in the history
  • Loading branch information
Robbepop committed Mar 20, 2019
1 parent ad8c066 commit c1fef8f
Show file tree
Hide file tree
Showing 6 changed files with 381 additions and 4 deletions.
9 changes: 5 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[workspace]

members = [
"core",
"model",
"lang",
"core",
"model",
"lang",
"cli",
]
exclude = [
"examples/",
"examples/",
]

[profile.release]
Expand Down
19 changes: 19 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "cargo-pdsl"
version = "0.1.0"
authors = ["Robin Freyler <robin@parity.io>", "Parity Technologies <admin@parity.io>"]
edition = "2018"

license = "GPL-3.0"
readme = "README.md"

description = "Setup and deployment tool for developing Wasm based smart contracts via pDSL"
keywords = ["wasm", "parity", "webassembly", "blockchain", "edsl"]
categories = ["cli", "tool"]

include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"]

[dependencies]
structopt = "0.2.15"
itertools = "0.8"
heck = "0.3"
38 changes: 38 additions & 0 deletions cli/src/cmd/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::{
io::Error as IoError,
result::{
Result as StdResult
},
};

/// The kinds of command errors.
#[derive(Debug)]
pub enum CommandErrorKind {
Io(IoError),
UnimplementedCommand,
UnimplementedAbstractionLayer,
}

/// An error that can be encountered while executing commands.
#[derive(Debug)]
pub struct CommandError {
kind: CommandErrorKind,
}

impl From<IoError> for CommandError {
fn from(error: IoError) -> Self {
Self {
kind: CommandErrorKind::Io(error),
}
}
}

impl CommandError {
/// Creates a new command error from the given kind.
pub fn new(kind: CommandErrorKind) -> Self {
Self { kind }
}
}

/// Result type that has a `CommandError`.
pub type Result<T> = StdResult<T, CommandError>;
11 changes: 11 additions & 0 deletions cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mod error;
mod new;

pub(crate) use self::{
error::{
CommandErrorKind,
CommandError,
Result,
},
new::execute_new,
};
189 changes: 189 additions & 0 deletions cli/src/cmd/new.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
use crate::{
cmd::{
CommandError,
CommandErrorKind,
Result,
},
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]
pdsl_core = {{ path = "../../../core" }}
pdsl_model = {{ path = "../../../model" }}
pdsl_lang = {{ path = "../../../lang" }}
parity-codec = {{ version = "3.1", default-features = false, features = ["derive"] }}
[lib]
name = "{}"
crate-type = ["cdylib"]
[features]
default = []
test-env = [
"pdsl_core/test-env",
"pdsl_model/test-env",
"pdsl_lang/test-env",
]
[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##"#![no_std]
use pdsl_core::storage;
use pdsl_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) {{
if *self.value {{
self.value.set(false)
}} else {{
self.value.set(true)
}}
}}
/// Returns the current state.
pub(external) fn get(&self) -> bool {{
*self.value
}}
}}
}}"##,
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
PROJNAME={}
CARGO_INCREMENTAL=0 &&
cargo +nightly build --release --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"##,
name
)
}

/// 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),
)?;

Ok(())
}

pub(crate) fn execute_new(layer: &AbstractionLayer, name: &str) -> Result<()> {
match layer {
AbstractionLayer::Core => {
Err(CommandError::new(
CommandErrorKind::UnimplementedAbstractionLayer,
))
}
AbstractionLayer::Model => {
Err(CommandError::new(
CommandErrorKind::UnimplementedAbstractionLayer,
))
}
AbstractionLayer::Lang => initialize_for_lang(name),
}
}
119 changes: 119 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of pDSL.
//
// pDSL is free software: you can redistribute it and/or modify
// 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.
//
// pDSL is distributed in the hope that it will be useful,
// 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
// along with pDSL. If not, see <http://www.gnu.org/licenses/>.

mod cmd;

use structopt::{
clap::AppSettings,
StructOpt,
};

#[derive(Debug, StructOpt)]
#[structopt(bin_name = "cargo")]
pub(crate) enum Opts {
#[structopt(
name = "contract",
raw(
setting = "AppSettings::UnifiedHelpMessage",
setting = "AppSettings::DeriveDisplayOrder",
setting = "AppSettings::DontCollapseArgsInUsage"
)
)]
/// Utilities to develop Wasm smart contracts.
Contract(ContractArgs),
}

#[derive(Debug, StructOpt)]
pub(crate) struct ContractArgs {
#[structopt(subcommand)]
cmd: Command,
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) enum AbstractionLayer {
Core,
Model,
Lang,
}

use std::result::Result as StdResult;

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) struct InvalidAbstractionLayer;

impl std::fmt::Display for InvalidAbstractionLayer {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "expected `core`, `model` or `lang`")
}
}

impl std::str::FromStr for AbstractionLayer {
type Err = InvalidAbstractionLayer;

fn from_str(input: &str) -> StdResult<Self, Self::Err> {
match input {
"core" => Ok(AbstractionLayer::Core),
"model" => Ok(AbstractionLayer::Model),
"lang" => Ok(AbstractionLayer::Lang),
_ => Err(InvalidAbstractionLayer),
}
}
}

#[derive(Debug, StructOpt)]
enum Command {
/// Setup and create a new smart contract.
#[structopt(name = "new")]
New {
/// The abstraction layer to use: `core`, `model` or `lang`
#[structopt(short = "l", long = "layer", default_value = "lang")]
layer: AbstractionLayer,
/// The name of the newly created smart contract.
name: String,
},
/// Builds the smart contract.
#[structopt(name = "build")]
Build {},
/// Test the smart contract off-chain.
#[structopt(name = "test")]
Test {},
/// Deploy the smart contract on-chain. (Also for testing purposes.)
#[structopt(name = "deploy")]
Deploy {
/// Deploy on a local development chain.
#[structopt(name = "dev", short, long)]
on_dev: bool
},
}

fn main() -> cmd::Result<()> {
let Opts::Contract(args) = Opts::from_args();
use crate::cmd::{CommandError, CommandErrorKind};
match &args.cmd {
Command::New { layer, name } => {
cmd::execute_new(layer, name)
}
Command::Build {} => {
Err(CommandError::new(CommandErrorKind::UnimplementedCommand))
}
Command::Test {} => {
Err(CommandError::new(CommandErrorKind::UnimplementedCommand))
}
Command::Deploy { .. } => {
Err(CommandError::new(CommandErrorKind::UnimplementedCommand))
}
}
}

0 comments on commit c1fef8f

Please sign in to comment.