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

[Feature request] Support global color overrides #33

Closed
sunshowers opened this issue Oct 28, 2021 · 14 comments · Fixed by #34
Closed

[Feature request] Support global color overrides #33

sunshowers opened this issue Oct 28, 2021 · 14 comments · Fixed by #34

Comments

@sunshowers
Copy link
Collaborator

sunshowers commented Oct 28, 2021

Hey there, thanks for writing owo-colors!

I'd love to use it except I'm running into one roadblock -- I wish owo-colors supported a global override for when e.g. a --color=always option is passed in to override any environment variables.

Even better would be to support thread-local and task-local overrides, which would make it easy to use owo-colors in libraries too.

@jam1garner
Copy link
Owner

@sunshowers if you don't mind me asking--what do you hope for this to look like API-wise? I definitely don't mind supporting the usecase, I just am curious what you're imagining.

One possibility I could see is pulling it into the if_supports_color API, in which case it'd then be a question of if this should be a part of owo-colors or if it should be a part of supports-color (the crate that if_supports_color uses internally).

lmk what you think!

@sunshowers
Copy link
Collaborator Author

sunshowers commented Oct 29, 2021

@jam1garner thanks!

I think:

@sunshowers
Copy link
Collaborator Author

Actually a method with a callback is better because forgetting RAII guards can screw things up.

@jam1garner
Copy link
Owner

@sunshowers alright so I started working on implementing this, but I think I should note: by default owo-colors does not do any checking if colors should be enabled/disabled, and that is only present if you use the if_supports_color API.

So I guess a few questions to help understand working towards a solution for you:

  1. Is if_supports_color sufficient for your usecase if such an override API was provided? (Most owo-colors users end up abstracting this away in some form)
  2. If not, I imagine you'd want it to function more like colored where it automatically checks this on every display operation, what would be your expectation as to the behavior? Part of why I didn't go this route originally was because assuming stdout vs stderr vs other wasn't something I wanted to do, but I'd be willing to put it behind a feature if you had a design idea

(Additional context: one usecase of owo-colors is doing things like outputting colors over debug for an embedded system, but even outside of that I find it hard to assume stdout (more common) or stderr (often colored because errors/warnings))

I'm definitely willing to support this feature (as well as whatever your usecase is), I just want to make sure I'm not assuming that if_supports_color is a good fit or if I should start by adding a feature for having something akin to if_supports_color be applied globally (and as I've said, if so, trying to understand what behavior users of such a feature would expect from it).

@sunshowers
Copy link
Collaborator Author

Thanks! How would a user of if_supports_color abstract over it? I don't fully understand the API

@jam1garner
Copy link
Owner

jam1garner commented Oct 30, 2021

So if_supports_color is used something like this:

println!(
    "{}: this was incorrectly configured because reasons"
    "Error".if_supports_color(Stdout, |text| text.red())
);

but typically for applications you can make assumptions about things like where you use colors and what output stream they're going to.

So you might do something like:

fn print_error(err: &impl fmt::Display) {
    eprintln!(
        "{}: {}",
        "Error".if_supports_color(Stderr, |x| x.red()),
        err
    );
}

Which is more convenient that having to pepper around "Error".if_supports_color(...) every error location. My point just being that usually by the time there's enough invocations of if_supports_error that it's annoying, your application is big enough that you'll end up abstracting your printing enough that all your coloring happens in 1-5 call sites anyways. (My example is somewhat bad because I imagine the Actual way error printing gets abstracted out is that all printable errors get bubbled up to a single match but hopefully you get the idea)

@sunshowers
Copy link
Collaborator Author

Ahh I see, that makes sense. What about using this for libraries?

@jam1garner
Copy link
Owner

jam1garner commented Oct 30, 2021

Well for libraries I suppose it varies a lot. If your library immediately pipes your colored output to println/eprintln then your library can make similar assumptions, if not I would imagine you'd probably want this behavior in the first place, otherwise you'd have to decide between your users seeing overcoloring (if you assumed Stdout but they displayed to Stderr) or undercoloring (if you only colored if both Stdout and Stderr were found to support colors). The only way I can really think of to deal with that is just to make it configurable for the library's user.

And to be clear--in a library which is providing configurable fancy/colored output I'd personally say to make a builder pattern for the printing, which would let you configure Stdout vs Stderr, something like ariadne's builder, which features print and eprint so it knows both where to output and would thus allow it to also figure out whether it that output stream supports color

Which, again, I could totally be wrong about what people's usecases are, and I'm definitely open to considering options to better support those whose usage doesn't fit these ideas

@jam1garner
Copy link
Owner

@sunshowers let me know your thoughts on #34. I'm going to spin off thread-local and task-local overrides into their own issues. Thread-local should be easy, but I can't said I've ever used task-locals before so I'll need to look into that.

Definitely let me know if either of those are something you need and if so I'll bump them up on my todo list.

@jam1garner jam1garner changed the title [Feature request] Support global (and maybe thread-local and task-local) overrides [Feature request] Support global color overrides Nov 1, 2021
@jam1garner
Copy link
Owner

Alright from my testing and from CI things look good. If this works well for you @sunshowers I'll publish these at 3.1.0, feel free to make another issue if what I have so far isn't enough, feel free to the PR if it has any issues for you, and if you have any thoughts on #35 or #36 please let me know! (Especially #36 as I have no idea what people would want/expect from task-locals, as I probably write 10x more sync code than async, but #35 is also open for comment on whether global or thread-local overrides should take priority)

@jam1garner
Copy link
Owner

Just to let you know @sunshowers this has been pushed in owo-colors 3.1.0

@sunshowers
Copy link
Collaborator Author

Thank you so much

@sunshowers
Copy link
Collaborator Author

sunshowers commented Dec 6, 2021

After a bit of experimentation, I started finding the supports colors stuff somewhat unwieldy to use unfortunately. Part of the problem is that my use case doesn't really fall under the print_error model: my use case has some pretty ad-hoc messages, such as:

error!("unrecognized registry URL {} found for {}",
    registry_url.if_supports_color(Stream::Stderr, |s| s.bold())),
    package.name().if_supports_color(Stream::Stderr, |s| s.bold())),
);

and so on. It seems like there's two approaches I can use in this case:

  1. Just give in and accept some boilerplate: define a method to abstract over, though auto-deref appears to make it so that I have to use an extension trait.
  2. Use the stylesheet approach: define a struct of styles somewhere and initialize it to certain colors. (That's basically what I've done for libraries anyway.)

I think correct usage on Unix terminals in this sort of ad-hoc situation necessarily has to imply not directly using the really simple coloring methods: .blue() etc.

(BTW I agree that the colored approach is incorrect because it assumes stdout -- but it's really clean in comparison since you don't need to use something like if_supports_colors).

@sunshowers
Copy link
Collaborator Author

(Note that the above only applies to these sorts of ad-hoc coloring schemes -- for more uniform ones if_supports_color works great!)

sunshowers added a commit to sunshowers/cargo-guppy that referenced this issue Dec 6, 2021
Some notes:
* We have to use the stylesheet approach: see the discussion in
jam1garner/owo-colors#33 (comment).
* This is an opportunity to clean up a bunch of output-related code --
  we can now switch to a type-level state machine from `OutputOpts` to
  `OutputContext`.
sunshowers added a commit to sunshowers/cargo-guppy that referenced this issue Dec 6, 2021
Some notes:
* We have to use the stylesheet approach: see the discussion in
jam1garner/owo-colors#33 (comment).
* This is an opportunity to clean up a bunch of output-related code --
  we can now switch to a type-level state machine from `OutputOpts` to
  `OutputContext`.
sunshowers added a commit to facebookarchive/cargo-guppy that referenced this issue Dec 6, 2021
Some notes:
* We have to use the stylesheet approach: see the discussion in
jam1garner/owo-colors#33 (comment).
* This is an opportunity to clean up a bunch of output-related code --
  we can now switch to a type-level state machine from `OutputOpts` to
  `OutputContext`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants