feat(cli): setup skeleton for quartz tool (#105)
Co-authored-by: Thane Thomson <connect@thanethomson.com>
This commit is contained in:
parent
a23ae1e560
commit
2afbe50f76
13 changed files with 332 additions and 1 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -2541,6 +2541,20 @@ dependencies = [
|
|||
"prost",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quartz"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"displaydoc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quartz-app-mtcs-enclave"
|
||||
version = "0.1.0"
|
||||
|
|
|
@ -3,6 +3,7 @@ resolver = "2"
|
|||
members = [
|
||||
"apps/mtcs/enclave",
|
||||
"apps/transfers/enclave",
|
||||
"cli",
|
||||
"core/light-client-proofs/*",
|
||||
"core/quartz",
|
||||
"cosmwasm/packages/*",
|
||||
|
@ -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"] }
|
||||
|
|
20
cli/Cargo.toml
Normal file
20
cli/Cargo.toml
Normal file
|
@ -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
cli/README.md
Normal file
76
cli/README.md
Normal file
|
@ -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
cli/src/cli.rs
Normal file
43
cli/src/cli.rs
Normal file
|
@ -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
cli/src/error.rs
Normal file
8
cli/src/error.rs
Normal file
|
@ -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
cli/src/handler.rs
Normal file
22
cli/src/handler.rs
Normal file
|
@ -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
cli/src/handler/init.rs
Normal file
16
cli/src/handler/init.rs
Normal file
|
@ -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
cli/src/main.rs
Normal file
60
cli/src/main.rs
Normal file
|
@ -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
cli/src/request.rs
Normal file
19
cli/src/request.rs
Normal file
|
@ -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
cli/src/request/init.rs
Normal file
30
cli/src/request/init.rs
Normal file
|
@ -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
cli/src/response.rs
Normal file
10
cli/src/response.rs
Normal file
|
@ -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
cli/src/response/init.rs
Normal file
12
cli/src/response/init.rs
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue