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");