Merge pull request #52 from iqlusioninc/cli
cli: Initial `yubikey-cli` utility with `list` command
This commit is contained in:
@@ -57,7 +57,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --release
|
args: --all --release
|
||||||
|
|
||||||
- name: Run cargo build --all-features
|
- name: Run cargo build --all-features
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
args: --all-features
|
args: --all --all-features
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test Suite
|
name: Test Suite
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
with:
|
with:
|
||||||
command: test
|
command: test
|
||||||
args: --release
|
args: --all --release
|
||||||
|
|
||||||
- name: Run cargo build --all-features
|
- name: Run cargo build --all-features
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
@@ -102,7 +102,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
with:
|
with:
|
||||||
command: build
|
command: build
|
||||||
args: --all-features
|
args: --all --all-features
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Rustfmt
|
name: Rustfmt
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: clippy
|
command: clippy
|
||||||
args: --all-features -- -D warnings
|
args: --all --all-features -- -D warnings
|
||||||
|
|
||||||
# TODO: use actions-rs/audit-check
|
# TODO: use actions-rs/audit-check
|
||||||
security_audit:
|
security_audit:
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
|
||||||
|
|||||||
Generated
+1000
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -13,7 +13,10 @@ license = "BSD-2-Clause"
|
|||||||
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
|
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
categories = ["api-bindings", "cryptography", "hardware-support"]
|
categories = ["api-bindings", "cryptography", "hardware-support"]
|
||||||
keywords = ["ccid", "ecdsa", "rsa", "piv", "yubikey"]
|
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [".", "cli"]
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
maintenance = { status = "experimental" }
|
maintenance = { status = "experimental" }
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "yubikey-cli"
|
||||||
|
version = "0.0.0"
|
||||||
|
description = """
|
||||||
|
Command-line interface for performing encryption and signing using RSA and/or
|
||||||
|
ECC keys stored on YubiKey devices.
|
||||||
|
"""
|
||||||
|
authors = ["Tony Arcieri <bascule@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "BSD-2-Clause"
|
||||||
|
repository = "https://github.com/iqlusioninc/yubikey-piv.rs"
|
||||||
|
readme = "README.md"
|
||||||
|
categories = ["command-line-utilities", "cryptography", "hardware-support"]
|
||||||
|
keywords = ["ecdsa", "rsa", "piv", "pcsc", "yubikey"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gumdrop = "0.7"
|
||||||
|
env_logger = "0.7"
|
||||||
|
lazy_static = "1"
|
||||||
|
termcolor = "1"
|
||||||
|
yubikey-piv = { version = "0.0.2", path = ".." }
|
||||||
+112
@@ -0,0 +1,112 @@
|
|||||||
|
<img src="https://raw.githubusercontent.com/tendermint/yubihsm-rs/develop/img/logo.png" width="150" height="110">
|
||||||
|
|
||||||
|
# yubikey-cli.rs
|
||||||
|
|
||||||
|
[![crate][crate-image]][crate-link]
|
||||||
|
[![Docs][docs-image]][docs-link]
|
||||||
|
![Apache2/MIT licensed][license-image]
|
||||||
|
![Rust Version][rustc-image]
|
||||||
|
![Maintenance Status: Experimental][maintenance-image]
|
||||||
|
[![Safety Dance][safety-image]][safety-link]
|
||||||
|
[![Build Status][build-image]][build-link]
|
||||||
|
[![Gitter Chat][gitter-image]][gitter-link]
|
||||||
|
|
||||||
|
Pure Rust host-side YubiKey [Personal Identity Verification (PIV)][PIV] CLI
|
||||||
|
utility with general-purpose public-key encryption and signing support.
|
||||||
|
|
||||||
|
[Documentation][docs-link]
|
||||||
|
|
||||||
|
## Minimum Supported Rust Version
|
||||||
|
|
||||||
|
- Rust **1.39+**
|
||||||
|
|
||||||
|
## Supported YubiKeys
|
||||||
|
|
||||||
|
- [YubiKey NEO] series (may be dropped in the future, see [#18])
|
||||||
|
- [YubiKey 4] series
|
||||||
|
- [YubiKey 5] series
|
||||||
|
|
||||||
|
NOTE: Nano and USB-C variants of the above are also supported
|
||||||
|
|
||||||
|
## Security Warning
|
||||||
|
|
||||||
|
No security audits of this crate have ever been performed. Presently it is in
|
||||||
|
an experimental stage and may still contain high-severity issues.
|
||||||
|
|
||||||
|
USE AT YOUR OWN RISK!
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
WIP. Check back later.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
We abide by the [Contributor Covenant][cc-md] and ask that you do as well.
|
||||||
|
|
||||||
|
For more information, please see [CODE_OF_CONDUCT.md][cc-md].
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) 2014-2019 Yubico AB, Tony Arcieri
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally
|
||||||
|
submitted for inclusion in the work by you shall be licensed under the
|
||||||
|
[2-Clause BSD License][BSDL] as shown above, without any additional terms
|
||||||
|
or conditions.
|
||||||
|
|
||||||
|
[//]: # (badges)
|
||||||
|
|
||||||
|
[crate-image]: https://img.shields.io/crates/v/yubikey-cli.svg
|
||||||
|
[crate-link]: https://crates.io/crates/yubikey-cli
|
||||||
|
[docs-image]: https://docs.rs/yubikey-cli/badge.svg
|
||||||
|
[docs-link]: https://docs.rs/yubikey-cli/
|
||||||
|
[license-image]: https://img.shields.io/badge/license-BSD-blue.svg
|
||||||
|
[rustc-image]: https://img.shields.io/badge/rustc-1.39+-blue.svg
|
||||||
|
[maintenance-image]: https://img.shields.io/badge/maintenance-experimental-blue.svg
|
||||||
|
[safety-image]: https://img.shields.io/badge/unsafe-forbidden-success.svg
|
||||||
|
[safety-link]: https://github.com/rust-secure-code/safety-dance/
|
||||||
|
[build-image]: https://github.com/iqlusioninc/yubikey-cli.rs/workflows/Rust/badge.svg?branch=develop&event=push
|
||||||
|
[build-link]: https://github.com/iqlusioninc/yubikey-cli.rs/actions
|
||||||
|
[gitter-image]: https://badges.gitter.im/badge.svg
|
||||||
|
[gitter-link]: https://gitter.im/iqlusioninc/community
|
||||||
|
|
||||||
|
[//]: # (general links)
|
||||||
|
|
||||||
|
[PIV]: https://piv.idmanagement.gov/
|
||||||
|
[yk-guide]: https://developers.yubico.com/PIV/Introduction/YubiKey_and_PIV.html
|
||||||
|
[Yubico]: https://www.yubico.com/
|
||||||
|
[YubiKey NEO]: https://support.yubico.com/support/solutions/articles/15000006494-yubikey-neo
|
||||||
|
[YubiKey 4]: https://support.yubico.com/support/solutions/articles/15000006486-yubikey-4
|
||||||
|
[YubiKey 5]: https://www.yubico.com/products/yubikey-5-overview/
|
||||||
|
[yubico-piv-tool]: https://github.com/Yubico/yubico-piv-tool/
|
||||||
|
[Corrode]: https://github.com/jameysharp/corrode
|
||||||
|
[cc-web]: https://contributor-covenant.org/
|
||||||
|
[cc-md]: https://github.com/iqlusioninc/yubikey-cli.rs/blob/develop/CODE_OF_CONDUCT.md
|
||||||
|
[BSDL]: https://opensource.org/licenses/BSD-2-Clause
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
//! `yubikey` command-line utility
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![warn(
|
||||||
|
missing_docs,
|
||||||
|
rust_2018_idioms,
|
||||||
|
unused_lifetimes,
|
||||||
|
unused_qualifications
|
||||||
|
)]
|
||||||
|
|
||||||
|
use gumdrop::Options;
|
||||||
|
use yubikey_cli::commands::YubikeyCli;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
YubikeyCli::parse_args_default_or_exit().run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
//! Commands of the CLI application
|
||||||
|
|
||||||
|
pub mod list;
|
||||||
|
|
||||||
|
use self::list::ListCmd;
|
||||||
|
use crate::status;
|
||||||
|
use gumdrop::Options;
|
||||||
|
use std::env;
|
||||||
|
use std::process::exit;
|
||||||
|
use termcolor::ColorChoice;
|
||||||
|
|
||||||
|
/// The `yubikey` CLI utility
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub struct YubikeyCli {
|
||||||
|
/// Obtain help about the current command
|
||||||
|
#[options(short = "h", help = "print help message")]
|
||||||
|
pub help: bool,
|
||||||
|
|
||||||
|
/// Subcommand to execute.
|
||||||
|
#[options(command)]
|
||||||
|
pub command: Option<Commands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl YubikeyCli {
|
||||||
|
/// Run the underlying command type or print usage info and exit
|
||||||
|
pub fn run(&self) {
|
||||||
|
// TODO(tarcieri): make this more configurable
|
||||||
|
status::set_color_choice(ColorChoice::Auto);
|
||||||
|
|
||||||
|
// Only show logs if `RUST_LOG` is set
|
||||||
|
if env::var("RUST_LOG").is_ok() {
|
||||||
|
env_logger::builder().format_timestamp(None).init();
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.command {
|
||||||
|
Some(cmd) => cmd.run(),
|
||||||
|
None => println!("{}", Commands::usage()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Subcommands of this application
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub enum Commands {
|
||||||
|
/// `help` subcommand
|
||||||
|
#[options(help = "show help for a command")]
|
||||||
|
Help(HelpOpts),
|
||||||
|
|
||||||
|
/// `version` subcommand
|
||||||
|
#[options(help = "display version information")]
|
||||||
|
Version(VersionOpts),
|
||||||
|
|
||||||
|
/// `list` subcommand
|
||||||
|
#[options(help = "list detected readers")]
|
||||||
|
List(ListCmd),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Commands {
|
||||||
|
/// Run the given command
|
||||||
|
pub fn run(&self) {
|
||||||
|
match self {
|
||||||
|
Commands::Help(help) => help.run(),
|
||||||
|
Commands::Version(version) => version.run(),
|
||||||
|
Commands::List(list) => list.run(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Help options
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub struct HelpOpts {
|
||||||
|
#[options(free, help = "subcommand to get help for")]
|
||||||
|
free: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HelpOpts {
|
||||||
|
fn run(&self) {
|
||||||
|
if let Some(command) = self.free.first() {
|
||||||
|
if let Some(usage) = Commands::command_usage(command) {
|
||||||
|
println!("{}", usage);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", Commands::usage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Version options
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub struct VersionOpts {}
|
||||||
|
|
||||||
|
impl VersionOpts {
|
||||||
|
/// Display version information
|
||||||
|
pub fn run(&self) {
|
||||||
|
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//! List detected readers
|
||||||
|
|
||||||
|
use gumdrop::Options;
|
||||||
|
use std::process::exit;
|
||||||
|
use yubikey_piv::readers::Readers;
|
||||||
|
|
||||||
|
/// The `list` subcommand
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub struct ListCmd {}
|
||||||
|
|
||||||
|
impl ListCmd {
|
||||||
|
/// Run the `list` subcommand
|
||||||
|
pub fn run(&self) {
|
||||||
|
let mut readers = Readers::open().unwrap_or_else(|e| {
|
||||||
|
status_err!("couldn't open PC/SC context: {}", e);
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
let readers_iter = readers.iter().unwrap_or_else(|e| {
|
||||||
|
status_err!("couldn't enumerate PC/SC readers: {}", e);
|
||||||
|
exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
if readers_iter.len() == 0 {
|
||||||
|
status_err!("no YubiKeys detected!");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, reader) in readers_iter.enumerate() {
|
||||||
|
let name = reader.name();
|
||||||
|
let mut yubikey = match reader.open() {
|
||||||
|
Ok(yk) => yk,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let serial = yubikey.serial();
|
||||||
|
println!("{}: {} (serial: {})", i + 1, name, serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
//! `yubikey` command-line utility
|
||||||
|
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![warn(
|
||||||
|
missing_docs,
|
||||||
|
rust_2018_idioms,
|
||||||
|
unused_lifetimes,
|
||||||
|
unused_qualifications
|
||||||
|
)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod status;
|
||||||
|
|
||||||
|
pub mod commands;
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
//! Status messages
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
|
||||||
|
|
||||||
|
/// Print a success status message (in green if colors are enabled)
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! status_ok {
|
||||||
|
($status:expr, $msg:expr) => {
|
||||||
|
$crate::status::Status::new()
|
||||||
|
.justified()
|
||||||
|
.bold()
|
||||||
|
.color(termcolor::Color::Green)
|
||||||
|
.status($status)
|
||||||
|
.print_stdout($msg);
|
||||||
|
};
|
||||||
|
($status:expr, $fmt:expr, $($arg:tt)+) => {
|
||||||
|
$crate::status_ok!($status, format!($fmt, $($arg)+));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print a warning status message (in yellow if colors are enabled)
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! status_warn {
|
||||||
|
($msg:expr) => {
|
||||||
|
$crate::status::Status::new()
|
||||||
|
.bold()
|
||||||
|
.color(termcolor::Color::Yellow)
|
||||||
|
.status("warning:")
|
||||||
|
.print_stdout($msg);
|
||||||
|
};
|
||||||
|
($fmt:expr, $($arg:tt)+) => {
|
||||||
|
$crate::status_warn!(format!($fmt, $($arg)+));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print an error message (in red if colors are enabled)
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! status_err {
|
||||||
|
($msg:expr) => {
|
||||||
|
$crate::status::Status::new()
|
||||||
|
.bold()
|
||||||
|
.color(termcolor::Color::Red)
|
||||||
|
.status("error:")
|
||||||
|
.print_stderr($msg);
|
||||||
|
};
|
||||||
|
($fmt:expr, $($arg:tt)+) => {
|
||||||
|
$crate::status_err!(format!($fmt, $($arg)+));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// Color configuration
|
||||||
|
static ref COLOR_CHOICE: Mutex<Option<ColorChoice>> = Mutex::new(None);
|
||||||
|
|
||||||
|
/// Standard output
|
||||||
|
pub static ref STDOUT: StandardStream = StandardStream::stdout(get_color_choice());
|
||||||
|
|
||||||
|
/// Standard error
|
||||||
|
pub static ref STDERR: StandardStream = StandardStream::stderr(get_color_choice());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Obtain the color configuration.
|
||||||
|
///
|
||||||
|
/// Panics if no configuration has been provided.
|
||||||
|
fn get_color_choice() -> ColorChoice {
|
||||||
|
let choice = COLOR_CHOICE.lock().unwrap();
|
||||||
|
*choice
|
||||||
|
.as_ref()
|
||||||
|
.expect("terminal stream accessed before initialized!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the color configuration.
|
||||||
|
///
|
||||||
|
/// Panics if the terminal has already been configured.
|
||||||
|
pub(super) fn set_color_choice(color_choice: ColorChoice) {
|
||||||
|
let mut choice = COLOR_CHOICE.lock().unwrap();
|
||||||
|
assert!(choice.is_none(), "terminal colors already configured!");
|
||||||
|
*choice = Some(color_choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Status message builder
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Status {
|
||||||
|
/// Should the status be justified?
|
||||||
|
justified: bool,
|
||||||
|
|
||||||
|
/// Should colors be bold?
|
||||||
|
bold: bool,
|
||||||
|
|
||||||
|
/// Color in which status should be displayed
|
||||||
|
color: Option<Color>,
|
||||||
|
|
||||||
|
/// Prefix of the status message (e.g. `Success`)
|
||||||
|
status: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Status {
|
||||||
|
/// Create a new status message with default settings
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Justify status on display
|
||||||
|
pub fn justified(mut self) -> Self {
|
||||||
|
self.justified = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make colors bold
|
||||||
|
pub fn bold(mut self) -> Self {
|
||||||
|
self.bold = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the colors used to display this message
|
||||||
|
pub fn color(mut self, c: Color) -> Self {
|
||||||
|
self.color = Some(c);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a status message to display
|
||||||
|
pub fn status<S>(mut self, msg: S) -> Self
|
||||||
|
where
|
||||||
|
S: ToString,
|
||||||
|
{
|
||||||
|
self.status = Some(msg.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print the given message to stdout
|
||||||
|
pub fn print_stdout(self, msg: impl AsRef<str>) {
|
||||||
|
self.print(&*STDOUT, msg)
|
||||||
|
.expect("error printing to stdout!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print the given message to stderr
|
||||||
|
pub fn print_stderr(self, msg: impl AsRef<str>) {
|
||||||
|
self.print(&*STDERR, msg)
|
||||||
|
.expect("error printing to stderr!")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print the given message
|
||||||
|
fn print(self, stream: &StandardStream, msg: impl AsRef<str>) -> Result<(), io::Error> {
|
||||||
|
let mut s = stream.lock();
|
||||||
|
s.reset()?;
|
||||||
|
s.set_color(ColorSpec::new().set_fg(self.color).set_bold(self.bold))?;
|
||||||
|
|
||||||
|
if let Some(status) = self.status {
|
||||||
|
if self.justified {
|
||||||
|
write!(s, "{:>12}", status)?;
|
||||||
|
} else {
|
||||||
|
write!(s, "{}", status)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.reset()?;
|
||||||
|
writeln!(s, " {}", msg.as_ref())?;
|
||||||
|
s.flush()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-2
@@ -164,9 +164,10 @@ pub mod settings;
|
|||||||
mod transaction;
|
mod transaction;
|
||||||
pub mod yubikey;
|
pub mod yubikey;
|
||||||
|
|
||||||
|
pub use self::{readers::Readers, yubikey::YubiKey};
|
||||||
|
|
||||||
#[cfg(feature = "untested")]
|
#[cfg(feature = "untested")]
|
||||||
pub use self::{key::Key, mgm::MgmKey, readers::Readers};
|
pub use self::{key::Key, mgm::MgmKey};
|
||||||
pub use yubikey::YubiKey;
|
|
||||||
|
|
||||||
/// Object identifiers
|
/// Object identifiers
|
||||||
pub type ObjectId = u32;
|
pub type ObjectId = u32;
|
||||||
|
|||||||
Reference in New Issue
Block a user