Skip to content

Commit a0a9ebd

Browse files
author
Joe Ellis
committed
Add initial parsec-tool implementation
Signed-off-by: Joe Ellis <[email protected]>
1 parent a3f8a55 commit a0a9ebd

16 files changed

+1575
-32
lines changed

Cargo.lock

+1,050
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,19 @@ edition = "2018"
1212
documentation = "https://docs.rs/crate/parsec-tool"
1313

1414
[dependencies]
15+
ansi_term = "0.12"
16+
anyhow = "1.0.31"
17+
atty = "0.2"
18+
clap = "3.0.0-beta.1"
19+
parsec-client = "0.7.1"
20+
parsec-interface = "0.19.0"
21+
secrecy = { version = "0.6.0", features = ["serde"] }
22+
structopt = "0.3.15"
23+
thiserror = "1.0"
24+
25+
[lib]
26+
name = "parsec_tool"
27+
path = "src/lib.rs"
28+
29+
[[bin]]
30+
name = "parsec-tool"

MAINTAINERS.toml

+5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"ionut-arm",
1818
"justincormack",
1919
"paulhowardarm",
20+
"joechrisellis",
2021
]
2122

2223
[people]
@@ -43,3 +44,7 @@
4344
4445
GitHub = "paulhowardarm"
4546

47+
[people.joechrisellis]
48+
Name = "Joe Ellis"
49+
50+
GitHub = "joechrisellis"

README.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ service](https://github.com/parallaxsecond/parsec) on the command-line.
1212
The software is provided under Apache-2.0. Contributions to this project are accepted under the same license.
1313

1414
This project uses the following third party crates:
15-
*
15+
* ansi\_term (MIT)
16+
* anyhow (MIT or Apache-2.0)
17+
* atty (MIT)
18+
* clap (MIT)
19+
* secrecy (MIT or Apache-2.0)
20+
* structopt (MIT or Apache-2.0)
21+
* thiserror (MIT or Apache-2.0)
1622

1723
## Contributing
1824

src/cli/mod.rs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Base CLI implementation.
5+
6+
use crate::common::{PROJECT_AUTHOR, PROJECT_DESC, PROJECT_NAME, PROJECT_VERSION};
7+
use crate::error::ParsecToolError;
8+
use crate::subcommands::{ParsecToolSubcommand, Subcommand};
9+
use parsec_client::auth::AuthenticationData;
10+
use structopt::StructOpt;
11+
12+
/// Struct representing the command-line interface of parsec-tool.
13+
#[derive(Debug, StructOpt)]
14+
#[structopt(name=PROJECT_NAME, about=PROJECT_DESC, author=PROJECT_AUTHOR, version=PROJECT_VERSION)]
15+
pub struct ParsecToolApp {
16+
/// How verbose should we be?
17+
#[structopt(short = "v", multiple = true, default_value = "0")]
18+
pub verbosity: u8,
19+
20+
/// Sets the authentication secret -- will default to no authentication if unspecified.
21+
#[structopt(short = "a", long = "auth-secret")]
22+
pub auth_secret: Option<String>,
23+
24+
/// The subcommand -- e.g., ping.
25+
#[structopt(subcommand)]
26+
pub subcommand: Subcommand,
27+
}
28+
29+
impl ParsecToolApp {
30+
/// Run the requested subcommand.
31+
pub fn dispatch_subcommand(&self) -> Result<(), ParsecToolError> {
32+
match &self.subcommand {
33+
Subcommand::Ping(cmd) => cmd.run(self),
34+
Subcommand::ListProviders(cmd) => cmd.run(self),
35+
Subcommand::ListOpcodes(cmd) => cmd.run(self),
36+
}
37+
}
38+
39+
/// Given an optional string, generate the corresponding AuthenticationData instance. This is
40+
/// effectively a `FromStr` implementation for AuthenticationData. Passing in `None` will return
41+
/// AuthenticationData::None. Passing in `Some(s)` will give you an app identity whose secret is
42+
/// built from the string `s`.
43+
pub fn authentication_data(&self) -> AuthenticationData {
44+
match &self.auth_secret {
45+
None => AuthenticationData::None,
46+
Some(s) => AuthenticationData::AppIdentity(secrecy::Secret::new(s.into())),
47+
}
48+
}
49+
}

src/common.rs

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Common variables.
5+
6+
/// The project name from Cargo.
7+
pub const PROJECT_NAME: &str = env!("CARGO_PKG_NAME");
8+
9+
/// The project description from Cargo.
10+
pub const PROJECT_DESC: &str = env!("CARGO_PKG_DESCRIPTION");
11+
12+
/// The project author from Cargo.
13+
pub const PROJECT_AUTHOR: &str = env!("CARGO_PKG_AUTHORS");
14+
15+
/// The project version from Cargo.
16+
pub const PROJECT_VERSION: &str = env!("CARGO_PKG_VERSION");

src/error.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Error definitions/handling.
5+
6+
use parsec_interface::operations::NativeResult;
7+
use thiserror::Error;
8+
9+
/// Errors in parsec-tool.
10+
#[derive(Error, Debug)]
11+
pub enum ParsecToolError {
12+
/// Error emanating from the parsec_client crate.
13+
#[error(transparent)]
14+
ParsecClientError(#[from] parsec_client::error::Error),
15+
16+
/// Error emanating from the parsec_interface crate.
17+
#[error(transparent)]
18+
ParsecInterfaceError(#[from] parsec_interface::requests::ResponseStatus),
19+
20+
/// Unexpected native result error, for when we expected a particular type of result but get
21+
/// something else.
22+
#[error("Got an unexpected native result: {0:?}")]
23+
UnexpectedNativeResult(NativeResult),
24+
}

src/lib.rs

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Source code for the `parsec-tool` project. This is a command-line interface for interacting
5+
//! with the Parsec service.
6+
7+
#![deny(
8+
nonstandard_style,
9+
const_err,
10+
dead_code,
11+
improper_ctypes,
12+
non_shorthand_field_patterns,
13+
no_mangle_generic_items,
14+
overflowing_literals,
15+
path_statements,
16+
patterns_in_fns_without_body,
17+
private_in_public,
18+
unconditional_recursion,
19+
unused,
20+
unused_allocation,
21+
unused_comparisons,
22+
unused_parens,
23+
while_true,
24+
missing_debug_implementations,
25+
missing_docs,
26+
trivial_casts,
27+
trivial_numeric_casts,
28+
unused_extern_crates,
29+
unused_import_braces,
30+
unused_qualifications,
31+
unused_results,
32+
missing_copy_implementations
33+
)]
34+
// This one is hard to avoid.
35+
#![allow(clippy::multiple_crate_versions)]
36+
37+
#[macro_use]
38+
pub mod util;
39+
40+
pub mod cli;
41+
pub mod common;
42+
pub mod error;
43+
pub mod subcommands;

src/main.rs

+19-31
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,26 @@
11
// Copyright 2020 Contributors to the Parsec project.
22
// SPDX-License-Identifier: Apache-2.0
33

4-
#![deny(
5-
nonstandard_style,
6-
const_err,
7-
dead_code,
8-
improper_ctypes,
9-
non_shorthand_field_patterns,
10-
no_mangle_generic_items,
11-
overflowing_literals,
12-
path_statements,
13-
patterns_in_fns_without_body,
14-
private_in_public,
15-
unconditional_recursion,
16-
unused,
17-
unused_allocation,
18-
unused_comparisons,
19-
unused_parens,
20-
while_true,
21-
missing_debug_implementations,
22-
missing_docs,
23-
trivial_casts,
24-
trivial_numeric_casts,
25-
unused_extern_crates,
26-
unused_import_braces,
27-
unused_qualifications,
28-
unused_results,
29-
missing_copy_implementations
30-
)]
31-
// This one is hard to avoid.
32-
#![allow(clippy::multiple_crate_versions)]
4+
//! parsec-tool: a tool for interfacing with the Parsec service from the command-line.
335
34-
//! Parsec Tool: a tool to communicate with Parsec from the command-line
6+
use parsec_tool::err;
7+
8+
use anyhow::Context;
9+
use anyhow::Result;
10+
use parsec_tool::cli;
11+
use structopt::StructOpt;
12+
13+
fn run() -> Result<()> {
14+
let matches = cli::ParsecToolApp::from_args();
15+
matches
16+
.dispatch_subcommand()
17+
.context("Executing subcommand failed.")?;
18+
Ok(())
19+
}
3520

3621
fn main() {
37-
println!("Hello, Parsec!");
22+
if let Err(err) = run() {
23+
err!("{:?}", err);
24+
std::process::exit(1);
25+
}
3826
}

src/subcommands/common.rs

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Common facilities and options for subcommands.
5+
6+
use structopt::StructOpt;
7+
8+
/// Options for specifying a provider. Most, but not all subcommands require the user to do this,
9+
/// so it's useful to have these options shared.
10+
#[derive(Copy, Clone, Debug, StructOpt)]
11+
pub struct ProviderOpts {
12+
/// The provider to list opcodes for.
13+
#[structopt(short = "p", long = "provider", default_value = "0")]
14+
pub provider: u8,
15+
}

src/subcommands/list_opcodes.rs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Lists the supported opcodes for a given provider.
5+
6+
use crate::cli::ParsecToolApp;
7+
use crate::error::ParsecToolError;
8+
use crate::subcommands::common::ProviderOpts;
9+
use crate::subcommands::ParsecToolSubcommand;
10+
use parsec_client::core::interface::requests::ProviderID;
11+
use parsec_client::core::operation_client::OperationClient;
12+
use parsec_interface::operations::list_opcodes;
13+
use parsec_interface::operations::{NativeOperation, NativeResult};
14+
use std::convert::TryFrom;
15+
use structopt::StructOpt;
16+
17+
/// Lists the supported opcodes for a given provider.
18+
#[derive(Copy, Clone, Debug, StructOpt)]
19+
#[structopt(name = "list_opcodes")]
20+
pub struct ListOpcodesSubcommand {
21+
#[structopt(flatten)]
22+
provider_opts: ProviderOpts,
23+
}
24+
25+
impl TryFrom<ListOpcodesSubcommand> for NativeOperation {
26+
type Error = ParsecToolError;
27+
28+
fn try_from(list_opcodes_subcommand: ListOpcodesSubcommand) -> Result<Self, Self::Error> {
29+
// Trivially converted to a `NativeOperation`.
30+
Ok(NativeOperation::ListOpcodes(list_opcodes::Operation {
31+
provider_id: ProviderID::try_from(list_opcodes_subcommand.provider_opts.provider)?,
32+
}))
33+
}
34+
}
35+
36+
impl ParsecToolSubcommand for ListOpcodesSubcommand {
37+
/// Lists the supported opcodes for a given provider.
38+
fn run(&self, matches: &ParsecToolApp) -> Result<(), ParsecToolError> {
39+
let client = OperationClient::new();
40+
let native_result = client.process_operation(
41+
NativeOperation::try_from(*self)?,
42+
// We still use the core provider beacuse listing opcodes is a core operation. Note the
43+
// distinction between the provider we're _using_ and the provider we're querying.
44+
ProviderID::Core,
45+
&matches.authentication_data(),
46+
)?;
47+
48+
if let NativeResult::ListOpcodes(result) = native_result {
49+
info!(
50+
"Available opcodes for provider {:?}:",
51+
ProviderID::try_from(self.provider_opts.provider)?
52+
);
53+
for provider_opcode in result.opcodes {
54+
eprint_colored!(Blue, "*");
55+
eprintln!(" {:?}", provider_opcode);
56+
}
57+
Ok(())
58+
} else {
59+
Err(ParsecToolError::UnexpectedNativeResult(native_result))
60+
}
61+
}
62+
}

src/subcommands/list_providers.rs

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2020 Contributors to the Parsec project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Lists the available providers supported by the Parsec service.
5+
6+
pub use crate::cli::ParsecToolApp;
7+
use crate::error::ParsecToolError;
8+
use crate::subcommands::ParsecToolSubcommand;
9+
use parsec_client::core::interface::requests::ProviderID;
10+
use parsec_client::core::operation_client::OperationClient;
11+
use parsec_interface::operations::list_providers;
12+
use parsec_interface::operations::{NativeOperation, NativeResult};
13+
use std::convert::TryFrom;
14+
use structopt::StructOpt;
15+
16+
/// Lists the available providers supported by the Parsec service.
17+
#[derive(Copy, Clone, Debug, StructOpt)]
18+
#[structopt(name = "list_providers")]
19+
pub struct ListProvidersSubcommand {}
20+
21+
impl TryFrom<ListProvidersSubcommand> for NativeOperation {
22+
type Error = ParsecToolError;
23+
24+
fn try_from(_list_providers_subcommand: ListProvidersSubcommand) -> Result<Self, Self::Error> {
25+
// Trivially converted to a `NativeOperation`.
26+
Ok(NativeOperation::ListProviders(list_providers::Operation {}))
27+
}
28+
}
29+
30+
impl ParsecToolSubcommand for ListProvidersSubcommand {
31+
/// Lists the available providers supported by the Parsec service.
32+
fn run(&self, matches: &ParsecToolApp) -> Result<(), ParsecToolError> {
33+
let client = OperationClient::new();
34+
let native_result = client.process_operation(
35+
NativeOperation::try_from(*self)?,
36+
ProviderID::Core,
37+
&matches.authentication_data(),
38+
)?;
39+
40+
if let NativeResult::ListProviders(result) = native_result {
41+
info!("Available providers:");
42+
for provider in result.providers {
43+
title!("0x{:02x} ({})", provider.id as u32, provider.id);
44+
field!("Description", "{}", provider.description);
45+
field!(
46+
"Version",
47+
"{}.{}.{}",
48+
provider.version_maj,
49+
provider.version_min,
50+
provider.version_rev
51+
);
52+
field!(
53+
"Vendor",
54+
"{}",
55+
if !provider.vendor.is_empty() {
56+
provider.vendor
57+
} else {
58+
"Unspecified".to_string()
59+
},
60+
);
61+
field!("Vendor", "{}", provider.uuid);
62+
println!();
63+
}
64+
Ok(())
65+
} else {
66+
Err(ParsecToolError::UnexpectedNativeResult(native_result))
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)