smart_config_commands/
lib.rs

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