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
//! This crate allows quoting strings for use in output. It works similarly to
//! [`str::escape_debug`], but the result is meant to be shown to users. Simply
//! call [`Quote::quote`] on an argument passed to [`println!`] or a similar
//! macro to quote it.
//!
//! One of the primary uses for this crate is displaying paths losslessly.
//! Since [`Path`] has no [`Display`] implementation, it is usually output by
//! calling [`Path::display`] or [`Path::to_string_lossy`] beforehand. However,
//! both of those methods are lossy; they replace all invalid characters with
//! [`REPLACEMENT_CHARACTER`]. This crate escapes those invalid characters
//! instead, allowing them to always be displayed correctly.
//!
//! Unprintable characters are also escaped, to give unambiguous output. All
//! code points are supported, but the Unicode Standard does not define which
//! are unprintable. So, a typical subset is used that may change between minor
//! versions. Guarantees are made in the next section.
//!
//! # Format
//!
//! The format used to represent strings is different from typical [`Debug`]
//! output, because it is designed to show most paths correctly on any
//! platform. In particular, backslashes (`\`) will never be escaped, since
//! Windows uses them as directory separators. They exist in almost every path
//! users provide on Windows.
//!
//! In their place, curly braces (`{` and `}`) are substituted, since they
//! appear less frequently. Thus, normal paths should not require any escaping
//! at all. The intention is to make the result easily readable on any system.
//!
//! These are some examples of the quoting format:
//!
//! ```
//! use uniquote::Quote;
//!
//! assert_eq!(r#""foo bar""#,      "foo bar".quote().to_string());
//! assert_eq!(r#""foo{~n}bar""#,   "foo\nbar".quote().to_string());
//! assert_eq!(r#""foo{~u7f}bar""#, "foo\x7Fbar".quote().to_string());
//! assert_eq!(r#""foo{"}bar""#,    "foo\"bar".quote().to_string());
//! ```
//!
//! The only ASCII characters escaped are `"`, `{`, `}`, and [control
//! characters]. Other characters are not guaranteed to be quoted in a specific
//! way but will generally only be escaped if unprintable.
//!
//! # Features
//!
//! These features are optional and can be enabled or disabled in a
//! "Cargo.toml" file. Nightly features are unstable, since they rely on
//! unstable Rust features.
//!
//! ### Default Features
//!
//! - **alloc** -
//!   Provides implementations of [`Quote`] for types that require allocation.
//!   This feature is enabled automatically when the **std** feature is
//!   enabled.
//!
//! - **std** -
//!   Provides implementations of [`Quote`] for types that require the standard
//!   library. When this feature is disabled, this crate can be used in
//!   `#![no_std]` environments.
//!
//! # Examples
//!
//! Print arguments passed on the command line:
//!
//! ```
//! use std::env;
//!
//! use uniquote::Quote;
//!
//! for (i, arg) in env::args_os().enumerate() {
//! #   #[cfg(feature = "std")]
//!     println!("arg #{} is {}", i, arg.quote());
//! }
//! ```
//!
//! Create a descriptive error message:
//!
//! ```
//! use std::error::Error;
//! use std::fmt;
//! use std::fmt::Display;
//! use std::fmt::Formatter;
//! use std::path::PathBuf;
//!
//! use uniquote::Quote;
//!
//! #[derive(Debug)]
//! struct FileNotFoundError(PathBuf);
//!
//! # #[cfg(feature = "std")]
//! # {
//! impl Display for FileNotFoundError {
//!     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
//!         write!(f, "file not found at {}", self.0.quote())
//!     }
//! }
//!
//! impl Error for FileNotFoundError {}
//! # }
//! ```
//!
//! [control characters]: char::is_ascii_control
//! [`Debug`]: ::std::fmt::Debug
//! [`Display`]: ::std::fmt::Display
//! [`Path`]: ::std::path::Path
//! [`Path::display`]: ::std::path::Path::display
//! [`Path::to_string_lossy`]: ::std::path::Path::to_string_lossy
//! [`REPLACEMENT_CHARACTER`]: ::std::char::REPLACEMENT_CHARACTER

#![cfg_attr(uniquote_docs_rs, feature(doc_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
#![warn(unused_results)]

#[cfg(feature = "alloc")]
extern crate alloc;

mod escape;

mod formatter;
pub use formatter::Error;
pub use formatter::Formatter;
pub use formatter::Result;

mod quote;
pub use quote::Quote;

const QUOTE: char = '"';

const START_ESCAPE: char = '{';

const END_ESCAPE: char = '}';