1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
//! Command-line extensions for `smart-config` library.
//!
//! The extensions are as follows:
//!
//! - [Printing help](Printer::print_help()) for configuration params with optional filtering.
//! - [Debugging](Printer::print_debug()) param values and deserialization errors.
//!
//! All extensions are encapsulated in [`Printer`].
//!
//! # Examples
//!
//! See the crate readme or the `examples` dir for captured output samples.
//!
//! ## Printing help
//!
//! ```
//! use smart_config::ConfigSchema;
//! use smart_config_commands::Printer;
//!
//! let mut schema = ConfigSchema::default();
//! // Add configurations to the schema...
//!
//! Printer::stderr().print_help(&schema, |_| true)?;
//! # std::io::Result::Ok(())
//! ```
//!
//! ## Debugging param values
//!
//! ```
//! use smart_config::{ConfigSchema, ConfigRepository};
//! use smart_config_commands::Printer;
//!
//! let mut schema = ConfigSchema::default();
//! // Add configurations to the schema...
//! let mut repo = ConfigRepository::new(&schema);
//! // Add sources to the repository...
//!
//! Printer::stderr().print_debug(&repo, |param_ref| {
//! // Allows filtering output params
//! param_ref.canonical_path().starts_with("test.")
//! })?;
//! # std::io::Result::Ok(())
//! ```
// Documentation settings
#![doc(html_root_url = "https://docs.rs/smart-config-commands/0.3.0-pre")]
// Linter settings
#![warn(missing_docs)]
use std::{
collections::HashSet,
io,
io::{StderrLock, StdoutLock},
};
use anstream::{stream::RawStream, AutoStream};
use anstyle::{AnsiColor, Color, Style};
use smart_config::{
metadata::{AliasOptions, ParamMetadata},
ConfigRef,
};
mod debug;
mod help;
mod utils;
const CONFIG_PATH: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
/// Wrapper around an I/O writer. Will style the output with ANSI sequences if appropriate.
///
/// Internally, the printer is based on [`anstream`] / [`anstyle`]; see their docs to find out how styling support
/// is detected by default. (TL;DR: based on `NO_COLOR`, `CLICOLOR_FORCE` and `CLICOLOR` env vars, and whether
/// the output is a terminal.) If this detection doesn't work for you, you can always [create](Self::custom()) a fully custom `Printer`.
///
/// [`anstream`]: https://docs.rs/anstream/
/// [`anstyle`]: https://docs.rs/anstyle/
#[derive(Debug)]
pub struct Printer<W: RawStream> {
writer: AutoStream<W>,
}
impl Printer<StdoutLock<'static>> {
/// Creates a printer to stdout. The stdout is locked while the printer is alive!
pub fn stdout() -> Self {
Self {
writer: AutoStream::auto(io::stdout()).lock(),
}
}
}
impl Printer<StderrLock<'static>> {
/// Creates a printer to stderr. The stderr is locked while the printer is alive!
pub fn stderr() -> Self {
Self {
writer: AutoStream::auto(io::stderr()).lock(),
}
}
}
impl<W: RawStream> Printer<W> {
/// Creates a custom printer.
pub fn custom(writer: AutoStream<W>) -> Self {
Self { writer }
}
}
/// Reference to a parameter on a configuration.
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct ParamRef<'a> {
/// Reference to the configuration containing the param.
pub config: ConfigRef<'a>,
/// Param metadata.
pub param: &'static ParamMetadata,
}
impl<'a> ParamRef<'a> {
pub(crate) fn for_tag(config: ConfigRef<'a>) -> Self {
Self {
config,
param: config.metadata().tag.unwrap().param,
}
}
/// Returns canonical path to the param.
pub fn canonical_path(&self) -> String {
if self.config.prefix().is_empty() {
self.param.name.to_owned()
} else {
format!(
"{prefix}.{name}",
prefix = self.config.prefix(),
name = self.param.name
)
}
}
/// Iterates over all paths to the param.
pub fn all_paths(&self) -> impl Iterator<Item = (String, AliasOptions)> + '_ {
// Deduplicate paths so that duplicates aren't displayed in UI; `all_paths_for_param()` doesn't do this internally.
let mut known_paths = HashSet::new();
self.config
.all_paths_for_param(self.param)
.filter(move |(name, _)| known_paths.insert(name.clone()))
}
}
#[cfg(doctest)]
doc_comment::doctest!("../README.md");