##// END OF EJS Templates
tracked-key: remove the dual write and rename to tracked-hint...
tracked-key: remove the dual write and rename to tracked-hint The dual-write approach was mostly useless. As explained in the previous version of the help, the key had to be read twice before we could cache a value. However this "read twice" limitation actually also apply to any usage of the key. If some operation wants to rely of the "same value == same tracked set" property it would need to read the value before, and after running that operation (or at least, after, in all cases). So it cannot be sure the operation it did is "valid" until checking the key after the operation. As a resultat such operation can only be read-only or rollbackable. This reduce the utility of the "same value == same tracked set" a lot. So it seems simpler to drop the double write and to update the documentation to highlight that this file does not garantee race-free operation. As a result the "key" is demoted to a "hint". Documentation is updated accordingly. Differential Revision: https://phab.mercurial-scm.org/D12201

File last commit:

r49622:f19be290 default
r49644:6e559391 default
Show More
ui.rs
246 lines | 7.3 KiB | application/rls-services+xml | RustLexer
Simon Sapin
rhg: Add support for colored output...
r49584 use crate::color::ColorConfig;
use crate::color::Effect;
Raphaël Gomès
rhg: use `format_bytes!` for error messages...
r46598 use format_bytes::format_bytes;
Simon Sapin
rhg: Add support for colored output...
r49584 use format_bytes::write_bytes;
Simon Sapin
rhg: Pass a &Config to Ui::new...
r49581 use hg::config::Config;
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 use hg::errors::HgError;
Simon Sapin
rhg: Add support for HGPLAINEXPECT...
r49580 use hg::utils::files::get_bytes_from_os_string;
Antoine Cezar
rhg: add a `DebugData` `Command` to prepare the `rhg debugdata` subcommand...
r46099 use std::borrow::Cow;
Pulkit Goyal
rhg: add ui.plain() and check it before showing relative paths in status...
r48990 use std::env;
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 use std::io;
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 use std::io::{ErrorKind, Write};
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 pub struct Ui {
stdout: std::io::Stdout,
stderr: std::io::Stderr,
Simon Sapin
rhg: Add support for colored output...
r49584 colors: Option<ColorConfig>,
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 }
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
/// The kind of user interface error
pub enum UiError {
/// The standard output stream cannot be written to
StdoutError(io::Error),
/// The standard error stream cannot be written to
StderrError(io::Error),
}
/// The commandline user interface
impl Ui {
Simon Sapin
rhg: Add support for colored output...
r49584 pub fn new(config: &Config) -> Result<Self, HgError> {
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 Ok(Ui {
Simon Sapin
rhg: Add support for colored output...
r49584 // If using something else, also adapt `isatty()` below.
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 stdout: std::io::stdout(),
Simon Sapin
rhg: Add support for colored output...
r49584
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 stderr: std::io::stderr(),
Simon Sapin
rhg: Add support for colored output...
r49584 colors: ColorConfig::new(config)?,
Simon Sapin
rhg: Make Ui::new falliable, add Ui::new_infallible...
r49582 })
}
/// Default to no color if color configuration errors.
///
/// Useful when we’re already handling another error.
Simon Sapin
rhg: Add support for colored output...
r49584 pub fn new_infallible(config: &Config) -> Self {
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 Ui {
Simon Sapin
rhg: Add support for colored output...
r49584 // If using something else, also adapt `isatty()` below.
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 stdout: std::io::stdout(),
Simon Sapin
rhg: Add support for colored output...
r49584
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 stderr: std::io::stderr(),
Simon Sapin
rhg: Add support for colored output...
r49584 colors: ColorConfig::new(config).unwrap_or(None),
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 }
}
/// Returns a buffered handle on stdout for faster batch printing
/// operations.
pub fn stdout_buffer(&self) -> StdoutBuffer<std::io::StdoutLock> {
StdoutBuffer::new(self.stdout.lock())
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 }
/// Write bytes to stdout
pub fn write_stdout(&self, bytes: &[u8]) -> Result<(), UiError> {
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 let mut stdout = self.stdout.lock();
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stdout.write_all(bytes).or_else(handle_stdout_error)?;
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stdout.flush().or_else(handle_stdout_error)
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 }
/// Write bytes to stderr
pub fn write_stderr(&self, bytes: &[u8]) -> Result<(), UiError> {
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 let mut stderr = self.stderr.lock();
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stderr.write_all(bytes).or_else(handle_stderr_error)?;
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stderr.flush().or_else(handle_stderr_error)
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 }
Pulkit Goyal
rhg: add ui.plain() and check it before showing relative paths in status...
r48990
Simon Sapin
rhg: Add support for colored output...
r49584 /// Write bytes to stdout with the given label
///
/// Like the optional `label` parameter in `mercurial/ui.py`,
/// this label influences the color used for this output.
pub fn write_stdout_labelled(
&self,
bytes: &[u8],
label: &str,
) -> Result<(), UiError> {
if let Some(colors) = &self.colors {
if let Some(effects) = colors.styles.get(label.as_bytes()) {
if !effects.is_empty() {
return self
.write_stdout_with_effects(bytes, effects)
.or_else(handle_stdout_error);
}
}
}
self.write_stdout(bytes)
}
fn write_stdout_with_effects(
&self,
bytes: &[u8],
effects: &[Effect],
) -> io::Result<()> {
let stdout = &mut self.stdout.lock();
let mut write_line = |line: &[u8], first: bool| {
// `line` does not include the newline delimiter
if !first {
stdout.write_all(b"\n")?;
}
if line.is_empty() {
return Ok(());
}
/// 0x1B == 27 == 0o33
const ASCII_ESCAPE: &[u8] = b"\x1b";
write_bytes!(stdout, b"{}[0", ASCII_ESCAPE)?;
for effect in effects {
write_bytes!(stdout, b";{}", effect)?;
}
write_bytes!(stdout, b"m")?;
stdout.write_all(line)?;
write_bytes!(stdout, b"{}[0m", ASCII_ESCAPE)
};
let mut lines = bytes.split(|&byte| byte == b'\n');
if let Some(first) = lines.next() {
write_line(first, true)?;
for line in lines {
write_line(line, false)?
}
}
stdout.flush()
}
Simon Sapin
rhg: Fall back to Python if verbose status is requested by config...
r49344 /// Return whether plain mode is active.
Pulkit Goyal
rhg: add ui.plain() and check it before showing relative paths in status...
r48990 ///
/// Plain mode means that all configuration variables which affect
/// the behavior and output of Mercurial should be
/// ignored. Additionally, the output should be stable,
/// reproducible and suitable for use in scripts or applications.
///
/// The only way to trigger plain mode is by setting either the
/// `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
///
/// The return value can either be
/// - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
/// - False if feature is disabled by default and not included in HGPLAIN
/// - True otherwise
Simon Sapin
rhg: Add support for HGPLAINEXPECT...
r49580 pub fn plain(&self, feature: Option<&str>) -> bool {
plain(feature)
}
}
Simon Sapin
rhg: Add support for colored output...
r49584 pub fn plain(opt_feature: Option<&str>) -> bool {
Simon Sapin
rhg: Add support for HGPLAINEXPECT...
r49580 if let Some(except) = env::var_os("HGPLAINEXCEPT") {
opt_feature.map_or(true, |feature| {
get_bytes_from_os_string(except)
.split(|&byte| byte == b',')
.all(|exception| exception != feature.as_bytes())
})
} else {
Pulkit Goyal
rhg: add ui.plain() and check it before showing relative paths in status...
r48990 env::var_os("HGPLAIN").is_some()
}
Antoine Cezar
rhg: add RootCommand using hg-core FindRoot operation to prepare `hg root`...
r45592 }
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921
/// A buffered stdout writer for faster batch printing operations.
pub struct StdoutBuffer<W: Write> {
buf: io::BufWriter<W>,
}
impl<W: Write> StdoutBuffer<W> {
pub fn new(writer: W) -> Self {
let buf = io::BufWriter::new(writer);
Self { buf }
}
/// Write bytes to stdout buffer
pub fn write_all(&mut self, bytes: &[u8]) -> Result<(), UiError> {
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 self.buf.write_all(bytes).or_else(handle_stdout_error)
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 }
/// Flush bytes to stdout
pub fn flush(&mut self) -> Result<(), UiError> {
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 self.buf.flush().or_else(handle_stdout_error)
Antoine Cezar
rhg: add buffered stdout writing possibility...
r45921 }
}
Antoine Cezar
rhg: extract function handle_stdout_error...
r45925
/// Sometimes writing to stdout is not possible, try writing to stderr to
/// signal that failure, otherwise just bail.
fn handle_stdout_error(error: io::Error) -> Result<(), UiError> {
if let ErrorKind::BrokenPipe = error.kind() {
// This makes `| head` work for example
return Ok(());
}
let mut stderr = io::stderr();
stderr
Raphaël Gomès
rhg: use `format_bytes!` for error messages...
r46598 .write_all(&format_bytes!(
b"abort: {}\n",
error.to_string().as_bytes()
))
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 .map_err(UiError::StderrError)?;
Antoine Cezar
rhg: extract function handle_stdout_error...
r45925
Antoine Cezar
rhg: fix `clippy` warnings...
r46010 stderr.flush().map_err(UiError::StderrError)?;
Antoine Cezar
rhg: extract function handle_stdout_error...
r45925
Err(UiError::StdoutError(error))
}
Antoine Cezar
rhg: handle broken pipe error for stderr...
r45926
/// Sometimes writing to stderr is not possible.
fn handle_stderr_error(error: io::Error) -> Result<(), UiError> {
// A broken pipe should not result in a error
// like with `| head` for example
if let ErrorKind::BrokenPipe = error.kind() {
return Ok(());
}
Err(UiError::StdoutError(error))
}
Antoine Cezar
rhg: add a `DebugData` `Command` to prepare the `rhg debugdata` subcommand...
r46099
/// Encode rust strings according to the user system.
pub fn utf8_to_local(s: &str) -> Cow<[u8]> {
// TODO encode for the user's system //
let bytes = s.as_bytes();
Cow::Borrowed(bytes)
}
Simon Sapin
rhg: Add support for colored output...
r49584
Raphaël Gomès
rhg: signal when falling back in logs...
r49622 /// Decode user system bytes to Rust string.
pub fn local_to_utf8(s: &[u8]) -> Cow<str> {
// TODO decode from the user's system
String::from_utf8_lossy(s)
}
Simon Sapin
rhg: Add support for colored output...
r49584 /// Should formatted output be used?
///
/// Note: rhg does not have the formatter mechanism yet,
/// but this is also used when deciding whether to use color.
pub fn formatted(config: &Config) -> Result<bool, HgError> {
if let Some(formatted) = config.get_option(b"ui", b"formatted")? {
Ok(formatted)
} else {
isatty(config)
}
}
fn isatty(config: &Config) -> Result<bool, HgError> {
Ok(if config.get_bool(b"ui", b"nontty")? {
false
} else {
atty::is(atty::Stream::Stdout)
})
}