Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make all dependencies optional #100

Merged
merged 11 commits into from Nov 3, 2018
5 changes: 1 addition & 4 deletions .travis.yml
Expand Up @@ -8,10 +8,7 @@ rust:
before_script:
- pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH
script:
- cargo build --verbose
- cargo build --verbose --no-default-features
- cargo test --verbose
- cargo test --verbose --no-default-features
- cargo run -p ci
after_success:
- travis-cargo --only nightly doc-upload

Expand Down
13 changes: 9 additions & 4 deletions Cargo.toml
Expand Up @@ -16,12 +16,17 @@ keywords = ["logging", "log", "logger"]
[maintenance]
status = "actively-developed"

[workspace]
members = [
"ci"
]

[dependencies]
log = { version = "0.4", features = ["std"] }
regex = { version = "1.0.3", optional = true }
termcolor = "1"
humantime = "1.1"
atty = "0.2.5"
termcolor = { version = "1.0.2", optional = true }
humantime = { version = "1.1", optional = true }
atty = { version = "0.2.5", optional = true }

[[test]]
name = "regexp_filter"
Expand All @@ -32,4 +37,4 @@ name = "log-in-log"
harness = false

[features]
default = ["regex"]
default = ["termcolor", "atty", "humantime", "regex"]
7 changes: 7 additions & 0 deletions ci/Cargo.toml
@@ -0,0 +1,7 @@
[package]
name = "ci"
version = "0.0.0"
authors = ["The Rust Project Developers"]
publish = false

[dependencies]
37 changes: 37 additions & 0 deletions ci/src/main.rs
@@ -0,0 +1,37 @@
mod task;
mod permute;

fn main() {
let features = [
"termcolor",
"humantime",
"atty",
"regex",
];

// Run a default build
if !task::test(Default::default()) {
panic!("default test execution failed");
}

// Run a set of permutations
let failed = permute::all(&features)
.into_iter()
.filter(|features|
!task::test(task::TestArgs {
features: features.clone(),
default_features: false,
lib_only: true,
}))
.collect::<Vec<_>>();

if failed.len() > 0 {
for failed in failed {
eprintln!("FAIL: {:?}", failed);
}

panic!("test execution failed");
} else {
println!("test execution succeeded");
}
}
27 changes: 27 additions & 0 deletions ci/src/permute.rs
@@ -0,0 +1,27 @@
use std::collections::BTreeSet;

pub fn all<T>(input: &[T]) -> BTreeSet<BTreeSet<T>> where T: Ord + Eq + Clone {
let mut permutations = BTreeSet::new();

if input.len() == 0 {
return permutations;
}

permutations.insert(input.iter().cloned().collect());

if input.len() > 1 {
for t in input {
let mut p = input
.iter()
.filter(|pt| *pt != t)
.cloned()
.collect::<Vec<_>>();

for pt in all(&p) {
permutations.insert(pt);
}
}
}

permutations
}
79 changes: 79 additions & 0 deletions ci/src/task.rs
@@ -0,0 +1,79 @@
use std::collections::BTreeSet;
use std::process::{
Command,
Stdio,
};

pub type Feature = &'static str;

pub struct TestArgs {
pub features: BTreeSet<Feature>,
pub default_features: bool,
pub lib_only: bool,
}

impl Default for TestArgs {
fn default() -> Self {
TestArgs {
features: BTreeSet::new(),
default_features: true,
lib_only: false,
}
}
}

impl TestArgs {
fn features_string(&self) -> Option<String> {
if self.features.len() == 0 {
return None;
}

let s = self.features.iter().fold(String::new(), |mut s, f| {
if s.len() > 0 {
s.push_str(" ");
}
s.push_str(f);

s
});

Some(s)
}
}

pub fn test(args: TestArgs) -> bool {
let features = args.features_string();

let mut command = Command::new("cargo");

command
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.arg("test")
.arg("--verbose");

if !args.default_features {
command.arg("--no-default-features");
}

if args.lib_only {
command.arg("--lib");
}

if let Some(ref features) = features {
command.args(&["--features", features]);
}

println!("running {:?}", command);

let status = command
.status()
.expect("Failed to execute command");

if !status.success() {
eprintln!("test execution failed for features: {}", features.as_ref().map(AsRef::as_ref).unwrap_or(""));
false
} else {
true
}
}
2 changes: 2 additions & 0 deletions examples/custom_format.rs
Expand Up @@ -33,6 +33,8 @@ fn init_logger() {
let mut builder = Builder::from_env(env);

// Use a different format for writing log records
// The colors are only available when the `termcolor` dependency is (which it is by default)
#[cfg(feature = "termcolor")]
builder.format(|buf, record| {
let mut style = buf.style();
style.set_bg(fmt::Color::Yellow).set_bold(true);
Expand Down
34 changes: 34 additions & 0 deletions src/fmt/atty.rs
@@ -0,0 +1,34 @@
/*
This internal module contains the terminal detection implementation.

If the `atty` crate is available then we use it to detect whether we're
attached to a particular TTY. If the `atty` crate is not available we
assume we're not attached to anything. This effectively prevents styles
from being printed.
*/

#[cfg(feature = "atty")]
mod imp {
use atty;

pub(in ::fmt) fn is_stdout() -> bool {
atty::is(atty::Stream::Stdout)
}

pub(in ::fmt) fn is_stderr() -> bool {
atty::is(atty::Stream::Stderr)
}
}

#[cfg(not(feature = "atty"))]
mod imp {
pub(in ::fmt) fn is_stdout() -> bool {
false
}

pub(in ::fmt) fn is_stderr() -> bool {
false
}
}

pub(in ::fmt) use self::imp::*;
84 changes: 84 additions & 0 deletions src/fmt/humantime/extern_impl.rs
@@ -0,0 +1,84 @@
use std::fmt;
use std::time::SystemTime;

use humantime::{format_rfc3339_nanos, format_rfc3339_seconds};

use ::fmt::Formatter;

pub(in ::fmt) mod pub_use_in_super {
pub use super::*;
}

impl Formatter {
/// Get a [`Timestamp`] for the current date and time in UTC.
///
/// # Examples
///
/// Include the current timestamp with the log record:
///
/// ```
/// use std::io::Write;
///
/// let mut builder = env_logger::Builder::new();
///
/// builder.format(|buf, record| {
/// let ts = buf.timestamp();
///
/// writeln!(buf, "{}: {}: {}", ts, record.level(), record.args())
/// });
/// ```
///
/// [`Timestamp`]: struct.Timestamp.html
pub fn timestamp(&self) -> Timestamp {
Timestamp(SystemTime::now())
}

/// Get a [`PreciseTimestamp`] for the current date and time in UTC with nanos.
pub fn precise_timestamp(&self) -> PreciseTimestamp {
PreciseTimestamp(SystemTime::now())
}
}

/// An [RFC3339] formatted timestamp.
///
/// The timestamp implements [`Display`] and can be written to a [`Formatter`].
///
/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt
/// [`Display`]: https://doc.rust-lang.org/stable/std/fmt/trait.Display.html
/// [`Formatter`]: struct.Formatter.html
pub struct Timestamp(SystemTime);

/// An [RFC3339] formatted timestamp with nanos.
///
/// [RFC3339]: https://www.ietf.org/rfc/rfc3339.txt
#[derive(Debug)]
pub struct PreciseTimestamp(SystemTime);

impl fmt::Debug for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// A `Debug` wrapper for `Timestamp` that uses the `Display` implementation.
struct TimestampValue<'a>(&'a Timestamp);

impl<'a> fmt::Debug for TimestampValue<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}

f.debug_tuple("Timestamp")
.field(&TimestampValue(&self))
.finish()
}
}

impl fmt::Display for Timestamp {
fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result {
format_rfc3339_seconds(self.0).fmt(f)
}
}

impl fmt::Display for PreciseTimestamp {
fn fmt(&self, f: &mut fmt::Formatter)->fmt::Result {
format_rfc3339_nanos(self.0).fmt(f)
}
}
11 changes: 11 additions & 0 deletions src/fmt/humantime/mod.rs
@@ -0,0 +1,11 @@
/*
This internal module contains the timestamp implementation.

Its public API is available when the `humantime` crate is available.
*/

#[cfg_attr(feature = "humantime", path = "extern_impl.rs")]
#[cfg_attr(not(feature = "humantime"), path = "shim_impl.rs")]
mod imp;

pub(in ::fmt) use self::imp::*;
7 changes: 7 additions & 0 deletions src/fmt/humantime/shim_impl.rs
@@ -0,0 +1,7 @@
/*
Timestamps aren't available when we don't have a `humantime` dependency.
*/

pub(in ::fmt) mod pub_use_in_super {

}