Skip to content

Commit

Permalink
Add initial parsec-tool implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Joe Ellis <[email protected]>
  • Loading branch information
Joe Ellis committed Aug 5, 2020
1 parent a3f8a55 commit a0a9ebd
Show file tree
Hide file tree
Showing 16 changed files with 1,575 additions and 32 deletions.
1,050 changes: 1,050 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,19 @@ edition = "2018"
documentation = "https://docs.rs/crate/parsec-tool"

[dependencies]
ansi_term = "0.12"
anyhow = "1.0.31"
atty = "0.2"
clap = "3.0.0-beta.1"
parsec-client = "0.7.1"
parsec-interface = "0.19.0"
secrecy = { version = "0.6.0", features = ["serde"] }
structopt = "0.3.15"
thiserror = "1.0"

[lib]
name = "parsec_tool"
path = "src/lib.rs"

[[bin]]
name = "parsec-tool"
5 changes: 5 additions & 0 deletions MAINTAINERS.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"ionut-arm",
"justincormack",
"paulhowardarm",
"joechrisellis",
]

[people]
Expand All @@ -43,3 +44,7 @@
Email = "[email protected]"
GitHub = "paulhowardarm"

[people.joechrisellis]
Name = "Joe Ellis"
Email = "[email protected]"
GitHub = "joechrisellis"
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@ service](https://github.com/parallaxsecond/parsec) on the command-line.
The software is provided under Apache-2.0. Contributions to this project are accepted under the same license.

This project uses the following third party crates:
*
* ansi\_term (MIT)
* anyhow (MIT or Apache-2.0)
* atty (MIT)
* clap (MIT)
* secrecy (MIT or Apache-2.0)
* structopt (MIT or Apache-2.0)
* thiserror (MIT or Apache-2.0)

## Contributing

Expand Down
49 changes: 49 additions & 0 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

//! Base CLI implementation.
use crate::common::{PROJECT_AUTHOR, PROJECT_DESC, PROJECT_NAME, PROJECT_VERSION};
use crate::error::ParsecToolError;
use crate::subcommands::{ParsecToolSubcommand, Subcommand};
use parsec_client::auth::AuthenticationData;
use structopt::StructOpt;

/// Struct representing the command-line interface of parsec-tool.
#[derive(Debug, StructOpt)]
#[structopt(name=PROJECT_NAME, about=PROJECT_DESC, author=PROJECT_AUTHOR, version=PROJECT_VERSION)]
pub struct ParsecToolApp {
/// How verbose should we be?
#[structopt(short = "v", multiple = true, default_value = "0")]
pub verbosity: u8,

/// Sets the authentication secret -- will default to no authentication if unspecified.
#[structopt(short = "a", long = "auth-secret")]
pub auth_secret: Option<String>,

/// The subcommand -- e.g., ping.
#[structopt(subcommand)]
pub subcommand: Subcommand,
}

impl ParsecToolApp {
/// Run the requested subcommand.
pub fn dispatch_subcommand(&self) -> Result<(), ParsecToolError> {
match &self.subcommand {
Subcommand::Ping(cmd) => cmd.run(self),
Subcommand::ListProviders(cmd) => cmd.run(self),
Subcommand::ListOpcodes(cmd) => cmd.run(self),
}
}

/// Given an optional string, generate the corresponding AuthenticationData instance. This is
/// effectively a `FromStr` implementation for AuthenticationData. Passing in `None` will return
/// AuthenticationData::None. Passing in `Some(s)` will give you an app identity whose secret is
/// built from the string `s`.
pub fn authentication_data(&self) -> AuthenticationData {
match &self.auth_secret {
None => AuthenticationData::None,
Some(s) => AuthenticationData::AppIdentity(secrecy::Secret::new(s.into())),
}
}
}
16 changes: 16 additions & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

//! Common variables.
/// The project name from Cargo.
pub const PROJECT_NAME: &str = env!("CARGO_PKG_NAME");

/// The project description from Cargo.
pub const PROJECT_DESC: &str = env!("CARGO_PKG_DESCRIPTION");

/// The project author from Cargo.
pub const PROJECT_AUTHOR: &str = env!("CARGO_PKG_AUTHORS");

/// The project version from Cargo.
pub const PROJECT_VERSION: &str = env!("CARGO_PKG_VERSION");
24 changes: 24 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

//! Error definitions/handling.
use parsec_interface::operations::NativeResult;
use thiserror::Error;

/// Errors in parsec-tool.
#[derive(Error, Debug)]
pub enum ParsecToolError {
/// Error emanating from the parsec_client crate.
#[error(transparent)]
ParsecClientError(#[from] parsec_client::error::Error),

/// Error emanating from the parsec_interface crate.
#[error(transparent)]
ParsecInterfaceError(#[from] parsec_interface::requests::ResponseStatus),

/// Unexpected native result error, for when we expected a particular type of result but get
/// something else.
#[error("Got an unexpected native result: {0:?}")]
UnexpectedNativeResult(NativeResult),
}
43 changes: 43 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

//! Source code for the `parsec-tool` project. This is a command-line interface for interacting
//! with the Parsec service.
#![deny(
nonstandard_style,
const_err,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_in_public,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true,
missing_debug_implementations,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_results,
missing_copy_implementations
)]
// This one is hard to avoid.
#![allow(clippy::multiple_crate_versions)]

#[macro_use]
pub mod util;

pub mod cli;
pub mod common;
pub mod error;
pub mod subcommands;
50 changes: 19 additions & 31 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

#![deny(
nonstandard_style,
const_err,
dead_code,
improper_ctypes,
non_shorthand_field_patterns,
no_mangle_generic_items,
overflowing_literals,
path_statements,
patterns_in_fns_without_body,
private_in_public,
unconditional_recursion,
unused,
unused_allocation,
unused_comparisons,
unused_parens,
while_true,
missing_debug_implementations,
missing_docs,
trivial_casts,
trivial_numeric_casts,
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_results,
missing_copy_implementations
)]
// This one is hard to avoid.
#![allow(clippy::multiple_crate_versions)]
//! parsec-tool: a tool for interfacing with the Parsec service from the command-line.
//! Parsec Tool: a tool to communicate with Parsec from the command-line
use parsec_tool::err;

use anyhow::Context;
use anyhow::Result;
use parsec_tool::cli;
use structopt::StructOpt;

fn run() -> Result<()> {
let matches = cli::ParsecToolApp::from_args();
matches
.dispatch_subcommand()
.context("Executing subcommand failed.")?;
Ok(())
}

fn main() {
println!("Hello, Parsec!");
if let Err(err) = run() {
err!("{:?}", err);
std::process::exit(1);
}
}
15 changes: 15 additions & 0 deletions src/subcommands/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

//! Common facilities and options for subcommands.
use structopt::StructOpt;

/// Options for specifying a provider. Most, but not all subcommands require the user to do this,
/// so it's useful to have these options shared.
#[derive(Copy, Clone, Debug, StructOpt)]
pub struct ProviderOpts {
/// The provider to list opcodes for.
#[structopt(short = "p", long = "provider", default_value = "0")]
pub provider: u8,
}
62 changes: 62 additions & 0 deletions src/subcommands/list_opcodes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

//! Lists the supported opcodes for a given provider.
use crate::cli::ParsecToolApp;
use crate::error::ParsecToolError;
use crate::subcommands::common::ProviderOpts;
use crate::subcommands::ParsecToolSubcommand;
use parsec_client::core::interface::requests::ProviderID;
use parsec_client::core::operation_client::OperationClient;
use parsec_interface::operations::list_opcodes;
use parsec_interface::operations::{NativeOperation, NativeResult};
use std::convert::TryFrom;
use structopt::StructOpt;

/// Lists the supported opcodes for a given provider.
#[derive(Copy, Clone, Debug, StructOpt)]
#[structopt(name = "list_opcodes")]
pub struct ListOpcodesSubcommand {
#[structopt(flatten)]
provider_opts: ProviderOpts,
}

impl TryFrom<ListOpcodesSubcommand> for NativeOperation {
type Error = ParsecToolError;

fn try_from(list_opcodes_subcommand: ListOpcodesSubcommand) -> Result<Self, Self::Error> {
// Trivially converted to a `NativeOperation`.
Ok(NativeOperation::ListOpcodes(list_opcodes::Operation {
provider_id: ProviderID::try_from(list_opcodes_subcommand.provider_opts.provider)?,
}))
}
}

impl ParsecToolSubcommand for ListOpcodesSubcommand {
/// Lists the supported opcodes for a given provider.
fn run(&self, matches: &ParsecToolApp) -> Result<(), ParsecToolError> {
let client = OperationClient::new();
let native_result = client.process_operation(
NativeOperation::try_from(*self)?,
// We still use the core provider beacuse listing opcodes is a core operation. Note the
// distinction between the provider we're _using_ and the provider we're querying.
ProviderID::Core,
&matches.authentication_data(),
)?;

if let NativeResult::ListOpcodes(result) = native_result {
info!(
"Available opcodes for provider {:?}:",
ProviderID::try_from(self.provider_opts.provider)?
);
for provider_opcode in result.opcodes {
eprint_colored!(Blue, "*");
eprintln!(" {:?}", provider_opcode);
}
Ok(())
} else {
Err(ParsecToolError::UnexpectedNativeResult(native_result))
}
}
}
69 changes: 69 additions & 0 deletions src/subcommands/list_providers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright 2020 Contributors to the Parsec project.
// SPDX-License-Identifier: Apache-2.0

//! Lists the available providers supported by the Parsec service.
pub use crate::cli::ParsecToolApp;
use crate::error::ParsecToolError;
use crate::subcommands::ParsecToolSubcommand;
use parsec_client::core::interface::requests::ProviderID;
use parsec_client::core::operation_client::OperationClient;
use parsec_interface::operations::list_providers;
use parsec_interface::operations::{NativeOperation, NativeResult};
use std::convert::TryFrom;
use structopt::StructOpt;

/// Lists the available providers supported by the Parsec service.
#[derive(Copy, Clone, Debug, StructOpt)]
#[structopt(name = "list_providers")]
pub struct ListProvidersSubcommand {}

impl TryFrom<ListProvidersSubcommand> for NativeOperation {
type Error = ParsecToolError;

fn try_from(_list_providers_subcommand: ListProvidersSubcommand) -> Result<Self, Self::Error> {
// Trivially converted to a `NativeOperation`.
Ok(NativeOperation::ListProviders(list_providers::Operation {}))
}
}

impl ParsecToolSubcommand for ListProvidersSubcommand {
/// Lists the available providers supported by the Parsec service.
fn run(&self, matches: &ParsecToolApp) -> Result<(), ParsecToolError> {
let client = OperationClient::new();
let native_result = client.process_operation(
NativeOperation::try_from(*self)?,
ProviderID::Core,
&matches.authentication_data(),
)?;

if let NativeResult::ListProviders(result) = native_result {
info!("Available providers:");
for provider in result.providers {
title!("0x{:02x} ({})", provider.id as u32, provider.id);
field!("Description", "{}", provider.description);
field!(
"Version",
"{}.{}.{}",
provider.version_maj,
provider.version_min,
provider.version_rev
);
field!(
"Vendor",
"{}",
if !provider.vendor.is_empty() {
provider.vendor
} else {
"Unspecified".to_string()
},
);
field!("Vendor", "{}", provider.uuid);
println!();
}
Ok(())
} else {
Err(ParsecToolError::UnexpectedNativeResult(native_result))
}
}
}
Loading

0 comments on commit a0a9ebd

Please sign in to comment.