Skip to content

Commit

Permalink
Make all dependencies optional (#100)
Browse files Browse the repository at this point in the history
* make all dependencies optional

* add a script for running test permutations
  • Loading branch information
KodrAus committed Nov 3, 2018
1 parent d560c65 commit 15ed5c7
Show file tree
Hide file tree
Showing 16 changed files with 819 additions and 409 deletions.
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 {

}

0 comments on commit 15ed5c7

Please sign in to comment.