Skip to content

Commit

Permalink
feat(cli): setup skeleton for quartz tool (#105)
Browse files Browse the repository at this point in the history
Co-authored-by: Thane Thomson <[email protected]>
  • Loading branch information
hu55a1n1 and thanethomson authored Jul 23, 2024
1 parent a23ae1e commit 2afbe50
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ resolver = "2"
members = [
"apps/mtcs/enclave",
"apps/transfers/enclave",
"cli",
"core/light-client-proofs/*",
"core/quartz",
"cosmwasm/packages/*",
Expand Down Expand Up @@ -39,7 +40,7 @@ rand = { version = "0.8.5", default-features = false, features = ["getrandom"] }
rand_core = { version = "0.6", default-features = false, features = ["std"] }
reqwest = { version = "0.12.2", default-features = false, features = ["json", "rustls-tls"] }
serde = { version = "1.0.203", default-features = false, features = ["derive"] }
serde_json = { version = "1.0.94", default-features = false }
serde_json = { version = "1.0.94", default-features = false, features = ["alloc"] }
serde_with = { version = "3.4.0", default-features = false, features = ["hex", "macros"] }
sha2 = { version = "0.10.8", default-features = false }
subtle-encoding = { version = "0.5.1", default-features = false, features = ["bech32-preview"] }
Expand Down
20 changes: 20 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "quartz"
version.workspace = true
authors.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
keywords = ["blockchain", "cosmos", "tendermint", "cycles", "quartz"]
readme = "README.md"

[dependencies]
clap.workspace = true
color-eyre.workspace = true
displaydoc.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
tracing.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter"] }
76 changes: 76 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# quartz CLI

A CLI tool to manage Quartz applications. The `quartz` CLI tool is designed to streamline the development and deployment
process of Quartz applications.

It provides helpful information about each command and its options. To get a list of all available subcommands and their
descriptions, use the `--help` flag:

```shell
$ quartz --help

Quartz 0.1.0
A CLI tool to manage Quartz applications

USAGE:
quartz [SUBCOMMAND]

OPTIONS:
-h, --help Print help information
-V, --version Print version information

SUBCOMMANDS:
init Create base Quartz app directory from template
build Build the contract and enclave binaries
enclave Enclave subcommads to configure Gramine, build, sign, and start the enclave binary
contract Contract subcommads to build, deploy the WASM binary to the blockchain and call instantiate
handshake Run the handshake between the contract and enclave
```

## Installation

To install Quartz, ensure you have Rust and Cargo installed. Then run:

```shell
cargo install quartz
```

## Usage of subcommands

### Init

Initialize a new Quartz app directory structure with optional name and path arguments.

#### Usage

```shell
$ quartz init --help
quartz-init
Create base Quartz app directory from template

USAGE:
quartz init [OPTIONS]

OPTIONS:
-n, --name <NAME> Set the name of the Quartz app [default: <name of parent directory>]
-p, --path <PATH> Set the path where the Quartz app will be created [default: .]
-h, --help Print help information
```

#### Example

```shell
quartz init --name <app_name> --path <path>
```

This command will create the following directory structure at the specified path (or the current directory if no path is
provided):

```shell
$ tree /<path>/<app-name> -L 1
apps/transfers/
├── contracts/
├── enclave/
├── frontend/
└── README.md
```
43 changes: 43 additions & 0 deletions cli/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::path::PathBuf;

use clap::{Parser, Subcommand};
use tracing::metadata::LevelFilter;

#[derive(clap::Args, Debug, Clone)]
pub struct Verbosity {
/// Increase verbosity, can be repeated up to 2 times
#[arg(long, short, action = clap::ArgAction::Count)]
pub verbose: u8,
}

impl Verbosity {
pub fn to_level_filter(&self) -> LevelFilter {
match self.verbose {
0 => LevelFilter::INFO,
1 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
}
}
}

#[derive(Debug, Parser)]
#[command(version, long_about = None)]
pub struct Cli {
/// Increase log verbosity
#[clap(flatten)]
pub verbose: Verbosity,

/// Main command
#[command(subcommand)]
pub command: Command,
}

#[derive(Debug, Subcommand)]
pub enum Command {
/// Create an empty Quartz app from a template
Init {
/// path to create & init a quartz app, defaults to current path if unspecified
#[clap(long)]
path: Option<PathBuf>,
},
}
8 changes: 8 additions & 0 deletions cli/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use displaydoc::Display;
use thiserror::Error;

#[derive(Debug, Display, Error)]
pub enum Error {
/// specified path `{0}` is not a directory
PathNotDir(String),
}
22 changes: 22 additions & 0 deletions cli/src/handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use crate::{cli::Verbosity, error::Error, request::Request, response::Response};

pub mod init;

pub trait Handler {
type Error;
type Response;

fn handle(self, verbosity: Verbosity) -> Result<Self::Response, Self::Error>;
}

impl Handler for Request {
type Error = Error;
type Response = Response;

fn handle(self, verbosity: Verbosity) -> Result<Self::Response, Self::Error> {
match self {
Request::Init(request) => request.handle(verbosity),
}
.map(Into::into)
}
}
16 changes: 16 additions & 0 deletions cli/src/handler/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use tracing::trace;

use crate::{
cli::Verbosity, error::Error, handler::Handler, request::init::InitRequest,
response::init::InitResponse,
};

impl Handler for InitRequest {
type Error = Error;
type Response = InitResponse;

fn handle(self, _verbosity: Verbosity) -> Result<Self::Response, Self::Error> {
trace!("initializing directory structure...");
todo!()
}
}
60 changes: 60 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![warn(
clippy::checked_conversions,
clippy::panic,
clippy::panic_in_result_fn,
clippy::unwrap_used,
trivial_casts,
trivial_numeric_casts,
rust_2018_idioms,
unused_lifetimes,
unused_import_braces,
unused_qualifications
)]

pub mod cli;
pub mod error;
pub mod handler;
pub mod request;
pub mod response;

use clap::Parser;
use color_eyre::eyre::Result;
use tracing_subscriber::{util::SubscriberInitExt, EnvFilter};

use crate::{cli::Cli, handler::Handler, request::Request};

fn main() -> Result<()> {
color_eyre::install()?;

let args = Cli::parse();

let env_filter = EnvFilter::builder()
.with_default_directive(args.verbose.to_level_filter().into())
.from_env_lossy();

tracing_subscriber::fmt()
.with_target(false)
.with_writer(std::io::stderr)
.with_env_filter(env_filter)
.finish()
.init();

// The idea is to parse the input args and convert them into `Requests` which are
// correct-by-construction types that this tool can handle. All validation should happen during
// this conversion.
let request = Request::try_from(args.command)?;

// Each `Request` defines an associated `Handler` (i.e. logic) and `Response`. All handlers are
// free to log to the terminal and these logs are sent to `stderr`.
let response = request.handle(args.verbose)?;

// `Handlers` must use `Responses` to output to `stdout`.
println!(
"{}",
serde_json::to_string(&response).expect("infallible serializer")
);

Ok(())
}
19 changes: 19 additions & 0 deletions cli/src/request.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::{cli::Command, error::Error, request::init::InitRequest};

pub mod init;

#[derive(Clone, Debug)]
pub enum Request {
Init(InitRequest),
}

impl TryFrom<Command> for Request {
type Error = Error;

fn try_from(cmd: Command) -> Result<Self, Self::Error> {
match cmd {
Command::Init { path } => InitRequest::try_from(path),
}
.map(Into::into)
}
}
30 changes: 30 additions & 0 deletions cli/src/request/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::path::PathBuf;

use crate::{error::Error, request::Request};

#[derive(Clone, Debug)]
pub struct InitRequest {
// TODO(hu55a1n1): remove `allow(unused)` here once init handler is implemented
#[allow(unused)]
directory: PathBuf,
}

impl TryFrom<Option<PathBuf>> for InitRequest {
type Error = Error;

fn try_from(path: Option<PathBuf>) -> Result<Self, Self::Error> {
if let Some(path) = path {
if !path.is_dir() {
return Err(Error::PathNotDir(format!("{}", path.display())));
}
}

todo!()
}
}

impl From<InitRequest> for Request {
fn from(request: InitRequest) -> Self {
Self::Init(request)
}
}
10 changes: 10 additions & 0 deletions cli/src/response.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use serde::Serialize;

use crate::response::init::InitResponse;

pub mod init;

#[derive(Clone, Debug, Serialize)]
pub enum Response {
Init(InitResponse),
}
12 changes: 12 additions & 0 deletions cli/src/response/init.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use serde::Serialize;

use crate::response::Response;

#[derive(Clone, Debug, Serialize)]
pub struct InitResponse;

impl From<InitResponse> for Response {
fn from(response: InitResponse) -> Self {
Self::Init(response)
}
}

0 comments on commit 2afbe50

Please sign in to comment.