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",
|
"prost",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quartz"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"color-eyre",
|
||||||
|
"displaydoc",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quartz-app-mtcs-enclave"
|
name = "quartz-app-mtcs-enclave"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -3,6 +3,7 @@ resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"apps/mtcs/enclave",
|
"apps/mtcs/enclave",
|
||||||
"apps/transfers/enclave",
|
"apps/transfers/enclave",
|
||||||
|
"cli",
|
||||||
"core/light-client-proofs/*",
|
"core/light-client-proofs/*",
|
||||||
"core/quartz",
|
"core/quartz",
|
||||||
"cosmwasm/packages/*",
|
"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"] }
|
rand_core = { version = "0.6", default-features = false, features = ["std"] }
|
||||||
reqwest = { version = "0.12.2", default-features = false, features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.12.2", default-features = false, features = ["json", "rustls-tls"] }
|
||||||
serde = { version = "1.0.203", default-features = false, features = ["derive"] }
|
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"] }
|
serde_with = { version = "3.4.0", default-features = false, features = ["hex", "macros"] }
|
||||||
sha2 = { version = "0.10.8", default-features = false }
|
sha2 = { version = "0.10.8", default-features = false }
|
||||||
subtle-encoding = { version = "0.5.1", default-features = false, features = ["bech32-preview"] }
|
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