Implement proxy object where runtime configures output

This is another approach for providing access to stdin/out/err via
libimagrt::runtime::Runtime.

The Runtime object does configure which output gets returned (stdout if
stdout is a tty, else stderr).

With this we can change libimagrt to read/write the store from/to
stdin/stdout without the user noticing that she does not write to stdout
but stderr.
Reading from stdin is not possible then, though.
This commit is contained in:
Matthias Beyer 2018-03-01 18:18:23 +01:00
parent 50461b839a
commit 19e9dfe33c
3 changed files with 86 additions and 46 deletions

View File

@ -0,0 +1,66 @@
//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015-2018 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//! Proxy objects for std::io::Stdin, std::io::Stdout, std::io::Stderr
use std::fmt::Debug;
use std::io::Write;
/// Proxy object for output
///
/// This is returned by `Runtime::stdout()` does implement `Write`. So you can
/// `write!(rt.stdout(), "some things")` and it just works.
///
/// The `Runtime` has to decide whether the OutputProxy should write to stdout, stderr or simply be
/// a "sink" which does not write to either.
///
pub enum OutputProxy {
Out,
Err,
Sink,
}
impl Write for OutputProxy {
fn write(&mut self, buf: &[u8]) -> ::std::io::Result<usize> {
match *self {
OutputProxy::Out => ::std::io::stdout().write(buf),
OutputProxy::Err => ::std::io::stderr().write(buf),
OutputProxy::Sink => Ok(0),
}
}
fn flush(&mut self) -> ::std::io::Result<()> {
match *self {
OutputProxy::Out => ::std::io::stdout().flush(),
OutputProxy::Err => ::std::io::stderr().flush(),
OutputProxy::Sink => Ok(()),
}
}
}
impl Debug for OutputProxy {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
match *self {
OutputProxy::Out => write!(f, "OutputProxy(Stdout)"),
OutputProxy::Err => write!(f, "OutputProxy(Stderr)"),
OutputProxy::Sink => write!(f, "OutputProxy(Sink)"),
}
}
}

View File

@ -57,6 +57,7 @@ extern crate libimaginteraction;
pub mod error; pub mod error;
pub mod configuration; pub mod configuration;
pub mod logger; pub mod logger;
pub mod io;
pub mod runtime; pub mod runtime;
pub mod setup; pub mod setup;
pub mod spec; pub mod spec;

View File

@ -22,8 +22,6 @@ use std::process::Command;
use std::env; use std::env;
use std::process::exit; use std::process::exit;
use std::io::Stdin; use std::io::Stdin;
use std::io::Write;
use std::fmt::Debug;
pub use clap::App; pub use clap::App;
use toml::Value; use toml::Value;
@ -35,6 +33,7 @@ use error::RuntimeError;
use error::RuntimeErrorKind; use error::RuntimeErrorKind;
use error::ResultExt; use error::ResultExt;
use logger::ImagLogger; use logger::ImagLogger;
use io::OutputProxy;
use libimagerror::trace::*; use libimagerror::trace::*;
use libimagstore::store::Store; use libimagstore::store::Store;
@ -50,43 +49,8 @@ pub struct Runtime<'a> {
configuration: Option<Value>, configuration: Option<Value>,
cli_matches: ArgMatches<'a>, cli_matches: ArgMatches<'a>,
store: Store, store: Store,
resources: Resources, stdin_is_tty: bool,
} stdout_is_tty: bool,
/// Resources for standard output, error output and input stream
///
/// These resources are set depending whether stdin/stdout are TTYs or not, but they are always
/// provided.
struct Resources {
stdout : Box<Write>,
stderr : Box<Write>,
stdin : Option<Stdin>,
}
impl Debug for Resources {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
write!(f, "Resources(...)")
}
}
/// Builds a Resource object
///
/// If stdout is not a TTY, the Resources::stdout will actually point to stderr
/// If stdin is not a TTY, it will be None
fn aquire_resources() -> Resources {
Resources {
stdout: if ::atty::is(::atty::Stream::Stdout) {
Box::new(::std::io::stdout())
} else {
Box::new(::std::io::stderr())
},
stderr: Box::new(::std::io::stderr()),
stdin: if ::atty::is(::atty::Stream::Stdin) {
Some(::std::io::stdin())
} else {
None
},
}
} }
impl<'a> Runtime<'a> { impl<'a> Runtime<'a> {
@ -190,7 +154,8 @@ impl<'a> Runtime<'a> {
configuration: config, configuration: config,
rtp: rtp, rtp: rtp,
store: store, store: store,
resources: aquire_resources(), stdout_is_tty: ::atty::is(::atty::Stream::Stdout),
stdin_is_tty: ::atty::is(::atty::Stream::Stdin),
} }
}) })
.chain_err(|| RuntimeErrorKind::Instantiate) .chain_err(|| RuntimeErrorKind::Instantiate)
@ -480,16 +445,24 @@ impl<'a> Runtime<'a> {
.map(Command::new) .map(Command::new)
} }
pub fn stdout(&self) -> &Box<Write> { pub fn stdout(&self) -> OutputProxy {
&self.resources.stdout if self.stdout_is_tty {
OutputProxy::Out
} else {
OutputProxy::Err
}
} }
pub fn stderr(&self) -> &Box<Write> { pub fn stderr(&self) -> OutputProxy {
&self.resources.stderr OutputProxy::Err
} }
pub fn stdin(&self) -> Option<&Stdin> { pub fn stdin(&self) -> Option<Stdin> {
self.resources.stdin.as_ref() if self.stdin_is_tty {
Some(::std::io::stdin())
} else {
None
}
} }
} }