├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── examples ├── custom.rs └── main.rs ├── media └── readme_example.png └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | Cargo.lock 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.4.0] - 2024-02-26 10 | - Update `term` to 0.7 11 | 12 | ## [0.3.0] - 2020-11-16 13 | ### Breaking change 14 | - Update `term` to 0.6 (this update contains fixes) 15 | - Changed `Color::Custom(u16)` to `Color::Custom(u32)` 16 | - Bump MSRV to 1.36 as part of the update of `term` 17 | 18 | ## [0.2.4] - 2017-08-22 19 | This release does not contain any changes. It was just used to update metadata on crates.io. 20 | 21 | ## [0.2.3] - 2016-11-23 22 | ### Added 23 | - Custom `u16` colors 24 | 25 | ### Changed 26 | - Improve documentation 27 | 28 | 29 | ## [0.2.2] - 2016-01-14 30 | ### Changed 31 | - Version wildcard in dependency was replaced by more specific version 32 | 33 | ### Fixed 34 | - Builds with `term 0.4` now 35 | 36 | 37 | ## [0.2.1] - 2015-08-27 38 | ### Fixed 39 | - `fmt` traits implementation: forward formatting parameter correctly 40 | 41 | ## [0.2.0] - 2015-07-10 42 | ### Added 43 | - `ToStyle::not_underline()` 44 | - Implementation for all `fmt` formatting traits, not just `Debug` and 45 | `Display` 46 | 47 | ### Changed 48 | - Improve documentation 49 | - Reduce size of `Style` from 14 to 4 bytes 50 | - Rename `Color::Normal` to `Color::NotSet` 51 | 52 | 53 | ## [0.1.1] - 2015-04-15 54 | ### Added 55 | - `ToStyle::with()` 56 | 57 | ### Changed 58 | - Improve documentation 59 | - Relax type constraints on `ToStyle` 60 | 61 | ### Fixed 62 | - Resetting styles works correctly now 63 | 64 | ## 0.1.0 - 2015-04-14 65 | *Initial release* 66 | 67 | [Unreleased]: https://github.com/olivierlacan/keep-a-changelog/compare/v0.3.0...HEAD 68 | [0.3.0]: https://github.com/LukasKalbertodt/term-painter/compare/0.2.4...0.3.0 69 | [0.2.4]: https://github.com/LukasKalbertodt/term-painter/compare/0.2.3...0.2.4 70 | [0.2.3]: https://github.com/LukasKalbertodt/term-painter/compare/0.2.2...0.2.3 71 | [0.2.2]: https://github.com/LukasKalbertodt/term-painter/compare/0.2.1...0.2.2 72 | [0.2.1]: https://github.com/LukasKalbertodt/term-painter/compare/0.2.0...0.2.1 73 | [0.2.0]: https://github.com/LukasKalbertodt/term-painter/compare/0.1.1...0.2.0 74 | [0.1.1]: https://github.com/LukasKalbertodt/term-painter/compare/0.1.0...0.1.1 75 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "term-painter" 3 | version = "0.4.0" 4 | authors = ["Lukas Kalbertodt "] 5 | 6 | description = "Coloring and formatting terminal output" 7 | documentation = "https://docs.rs/term-painter/" 8 | repository = "https://github.com/LukasKalbertodt/term-painter" 9 | readme = "README.md" 10 | license = "MIT/Apache-2.0" 11 | 12 | keywords = ["terminal", "color", "format", "paint"] 13 | categories = ["command-line-interface"] 14 | 15 | [dependencies] 16 | term = "0.7" 17 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 term-painter developer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coloring terminal output 2 | [![Build Status](https://img.shields.io/travis/LukasKalbertodt/term-painter/master.svg)](https://travis-ci.org/LukasKalbertodt/term-painter) 3 | [![crates.io version](https://img.shields.io/crates/v/term-painter.svg)](https://crates.io/crates/term-painter) 4 | [![GitHub license](https://img.shields.io/github/license/LukasKalbertodt/term-painter.svg)]() 5 | 6 | [**Documentation**](https://docs.rs/term-painter/) 7 | 8 | `term-painter` is a cross-platform (i.e. also non-ANSI terminals) Rust library for coloring and formatting terminal output. 9 | It provides easy ways to format various things and uses the crate [`rust-lang/term`][term] to do the actual formatting. **Please read ["When (not) to use this crate"](#when-not-to-use-this-crate)**. 10 | 11 | **Note**: I created another library for coloring terminal text: [`bunt`](https://crates.io/crates/bunt). I like it much better, so maybe consider using `bunt` instead of `term-painter` :-) 12 | 13 | Example: 14 | 15 | ``` Rust 16 | println!("{} | {} | {} | {} | {}", 17 | Red.bg(Green).bold().paint("Red-Green-Bold"), 18 | Blue.paint("Blue"), 19 | Blue.bold().paint("BlueBold"), 20 | Blue.bg(Magenta).paint("BlueMagentaBG"), 21 | Plain.underline().paint("Underline") 22 | ); 23 | 24 | Red.with(|| { 25 | print!("JustRed"); 26 | Bold.with(|| { 27 | print!(" BoldRed {} BoldRed ", Underline.paint("Underline")); 28 | }); 29 | print!("JustRed "); 30 | 31 | print!("{}", Blue.paint("Blue (overwrite) ")); 32 | Green.with(|| { 33 | println!("Green (overwrite)"); 34 | }); 35 | }); 36 | ``` 37 | 38 | ![alt text](https://raw.githubusercontent.com/LukasKalbertodt/term-painter/master/media/readme_example.png "Result of code snippet above") 39 | 40 | It's easy to use and integrates well with `println!`/`print!`. 41 | The main design goal was to make it simple. 42 | This has one performance disadvantage: It will often reset the terminal style after each printing operation. 43 | But performance isn't usually hugely important when printing on the terminal, so simplicity was more important for the design of this library. 44 | 45 | More examples [here (`examples/main.rs`)](https://github.com/LukasKalbertodt/term-painter/blob/master/examples/main.rs) or in the [**Documentation**](https://lukaskalbertodt.github.io/term-painter/term_painter/). 46 | 47 | ## When (not) to use this crate 48 | 49 | There are more terminal color crates than stars in the observable universe, therefore it's a valid question to ask "which one is best"? Unfortunately, there is no clear answer, I think. 50 | 51 | **_Don't_ use this crate, if:** 52 | - you want full power of what happens (consider using `rust-lang/term` instead), *or* 53 | - you want to print from multiple threads (consider using [`termcolor`](https://crates.io/crates/termcolor) or [`bunt`](https://crates.io/crates/bunt) instead), *or* 54 | - you want to color/format text you print on something else than stdout (however, `term-painter` might add support for stderr in the future) 55 | - you want an actively developed crate (see ["Status of this crate"](#status-of-this-crate)) 56 | - you want to use a crate with a fancy name (`term-painter` is such a boring name :unamused:) 57 | 58 | **You _probably shouldn't_ use this crate, if:** 59 | - you don't need to support non-ANSI terminals (Only supporting ANSI-formatting gives the author of the lib greater flexibility in designing the API, thus potentially making it easier and more powerful. See [section "Cross Platform"](#cross-platform). Consider using [`ansi-term`](https://crates.io/crates/ansi_term), [`colored`](https://crates.io/crates/colored), [`yansi`](https://crates.io/crates/yansi), ... instead.), *or* 60 | - you expect a time-proven library 61 | 62 | **Use this crate, if:** 63 | - you need support for non-ANSI terminals, *and* 64 | - you are developing a non-mission critical project 65 | 66 | ## Cross Platform 67 | 68 | This crate uses [`rust-lang/term`][term] internally. 69 | `term` supports all (or rather: many) platforms, hence `term-painter` does, too. 70 | 71 | *How does it work?* In order to work, this crate depends on a specific way how `println!` and friends evaluate their arguments (which is the common-sense way). 72 | There are no guarantees about argument evaluation, but currently it works. 73 | And honestly, it's unlikely that this way of evaluation ever changes. 74 | But, for example, if `println!()` would always call `format!()` first and print the resulting `String`, it wouldn't work. 75 | 76 | To give a simplified explanation of the terminal-world: there are ANSI-terminal and non-ANSI Terminals. ANSI-formatting works by inserting special control characters into the string. Thus you can easily store the formatted string in a `String` to print it later. Non-ANSI terminals work differently, and the most commonly used one is `cmd` which is part of Windows 7/10. Formatting for `cmd` works by calling methods of the winapi before and after printing. Thus you *cannot* easily store a formatted string. Apart from that, AFAIK there are not that many developers mainly using `cmd` -- most Windows developer use IDEs alone or another terminal for Windows (there are plenty). 77 | 78 | In summary: most terminals support ANSI-coloring, non-ANSI-terminals make the world more complicated. 79 | 80 | [term]: https://crates.io/crates/term 81 | 82 | ## Status of this crate 83 | 84 | This crate is not actively developed anymore. The API is likely to stay as it is. I doubt this crate will reach its 1.0 milestone. This doesn't mean that this crate doesn't work! You can still use it, if it fits your needs. 85 | 86 | ## Thanks 87 | I've got some design ideas from [`rust-ansi-term`](https://github.com/ogham/rust-ansi-term). 88 | I decided to make my own crate though, since my goals were too different from `ansi-term` (specifically: `ansi-term` does not work everywhere). 89 | 90 | --- 91 | 92 | ## License 93 | 94 | Licensed under either of 95 | 96 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 97 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 98 | 99 | at your option. 100 | 101 | ### Contribution 102 | 103 | Unless you explicitly state otherwise, any contribution intentionally submitted 104 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall 105 | be dual licensed as above, without any additional terms or conditions. 106 | -------------------------------------------------------------------------------- /examples/custom.rs: -------------------------------------------------------------------------------- 1 | extern crate term_painter; 2 | 3 | use term_painter::ToStyle; 4 | use term_painter::Color::Custom; 5 | use term_painter::Attr::Plain; 6 | 7 | fn main() { 8 | // print 16 colors each line 9 | for line in 0..16 { 10 | // foreground 11 | print!("FG: "); 12 | for c in (0..16).map(|i| 16*line + i) { 13 | print!("{: <2x} ", Custom(c).paint(c)); 14 | } 15 | println!(""); 16 | 17 | // background 18 | print!("BG: "); 19 | for c in (0..16).map(|i| 16*line + i) { 20 | print!("{: <2x} ", Plain.bg(Custom(c)).paint(c)); 21 | } 22 | println!(""); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/main.rs: -------------------------------------------------------------------------------- 1 | extern crate term_painter; 2 | 3 | use term_painter::{ToStyle, Color}; 4 | use term_painter::Color::*; 5 | use term_painter::Attr::*; 6 | 7 | fn main() { 8 | struct_sizes(); 9 | 10 | simple_examples(); 11 | with_example(); 12 | doc_examples(); 13 | 14 | all_styles( 15 | &[NotSet, Black, Red, Green, Yellow, Blue, Magenta, Cyan, White]); 16 | all_styles( 17 | &[BrightBlack, BrightRed, BrightGreen, BrightYellow, BrightBlue, 18 | BrightMagenta, BrightCyan, BrightWhite]); 19 | } 20 | 21 | fn struct_sizes() { 22 | use std::mem::size_of; 23 | 24 | println!("size_of(Style): {}", size_of::()); 25 | println!("size_of(Color): {}", size_of::()); 26 | println!("size_of(Attr): {}", size_of::()); 27 | } 28 | 29 | fn simple_examples() { 30 | println!("{} | {} | {} | {} | {}", 31 | Red.bg(Green).bold().paint("Red-Green-Bold"), 32 | Blue.paint("Blue"), 33 | Blue.bold().paint("BlueBold"), 34 | Blue.bg(Magenta).paint("BlueMagentaBG"), 35 | Plain.underline().paint("Underline")); 36 | } 37 | 38 | fn with_example() { 39 | Red.with(|| { 40 | print!("JustRed"); 41 | Bold.with(|| { 42 | print!(" BoldRed {} BoldRed ", Underline.paint("Underline")); 43 | }); 44 | print!("JustRed "); 45 | 46 | print!("{}", Blue.paint("Blue (overwrite) ")); 47 | Green.with(|| { 48 | println!("Green (overwrite)"); 49 | }); 50 | }); 51 | } 52 | 53 | fn doc_examples() { 54 | // --- Doc example 1 55 | println!("{} or {} or {}", 56 | Red.paint("Red"), 57 | Bold.paint("Bold"), 58 | Red.bold().paint("Both!")); 59 | 60 | // --- Doc example 2 61 | let x = 5; 62 | 63 | // These two are equivalent 64 | println!("{} | {}", x, Plain.paint(x)); 65 | 66 | // These two are equivalent, too 67 | println!("{} | {}", Red.paint(x), Plain.fg(Red).paint(x)); 68 | 69 | // --- Doc example 3 70 | let non_copy = "cake".to_string(); // String is *not* Copy 71 | let copy = 27; // usize/isize *is* Copy 72 | 73 | println!("{}", Plain.paint(&non_copy)); 74 | println!("{}", Plain.paint(©)); 75 | // non_copy is still usable here... 76 | // copy is still usable here... 77 | 78 | println!("{}", Plain.paint(non_copy)); 79 | println!("{}", Plain.paint(copy)); 80 | // non_copy was moved into paint, so it not usable anymore... 81 | // copy is still usable here... 82 | } 83 | 84 | fn all_styles(colors: &[Color]) { 85 | // Normal test 86 | for c in colors { print!("{:?} ", c.paint(c)); } 87 | println!(" (fg)"); 88 | for c in colors { print!("{:?} ", Plain.bg(*c).paint(c)); } 89 | println!(" (bg)"); 90 | 91 | // Bold text 92 | for c in colors { print!("{:?} ", c.bold().paint(c)); } 93 | println!(" (bold fg)"); 94 | for c in colors { print!("{:?} ", Bold.bg(*c).paint(c)); } 95 | println!(" (bold bg)"); 96 | 97 | // Dim text 98 | for c in colors { print!("{:?} ", c.dim().paint(c)); } 99 | println!(" (dim fg)"); 100 | for c in colors { print!("{:?} ", Dim.bg(*c).paint(c)); } 101 | println!(" (dim bg)"); 102 | 103 | // Underlined text 104 | for c in colors { print!("{:?} ", c.underline().paint(c)); } 105 | println!(" (underline fg)"); 106 | for c in colors { print!("{:?} ", Underline.bg(*c).paint(c)); } 107 | println!(" (underline bg)"); 108 | 109 | // Blinking text 110 | for c in colors { print!("{:?} ", c.blink().paint(c)); } 111 | println!(" (blink fg)"); 112 | for c in colors { print!("{:?} ", Blink.bg(*c).paint(c)); } 113 | println!(" (blink bg)"); 114 | 115 | // Reverse text 116 | for c in colors { print!("{:?} ", c.reverse().paint(c)); } 117 | println!(" (reverse fg)"); 118 | for c in colors { print!("{:?} ", Reverse.bg(*c).paint(c)); } 119 | println!(" (reverse bg)"); 120 | 121 | // Secure text 122 | for c in colors { print!("{:?} ", c.secure().paint(c)); } 123 | println!(" (secure fg)"); 124 | for c in colors { print!("{:?} ", Secure.bg(*c).paint(c)); } 125 | println!(" (secure bg)"); 126 | 127 | } 128 | -------------------------------------------------------------------------------- /media/readme_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LukasKalbertodt/term-painter/7a76399839073f4056642de867ff2252ba0c1b90/media/readme_example.png -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This is a crate for coloring and formatting terminal output. Simple 2 | //! example: 3 | //! 4 | //! ``` 5 | //! extern crate term_painter; 6 | //! 7 | //! use term_painter::ToStyle; 8 | //! use term_painter::Color::*; 9 | //! use term_painter::Attr::*; 10 | //! 11 | //! fn main() { 12 | //! println!("{} or {} or {}", 13 | //! Red.paint("Red"), 14 | //! Bold.paint("Bold"), 15 | //! Red.bold().paint("Both!") 16 | //! ); 17 | //! } 18 | //! ``` 19 | //! 20 | //! This crate uses [`rust-lang/term`][term] to do the formatting. Of course, 21 | //! you can use `term` directly, but it's kinda clumsy. Hence this library. 22 | //! 23 | //! [term]: https://crates.io/crates/term 24 | //! 25 | //! 26 | //! How to use it 27 | //! ------------- 28 | //! Formatting is done in two steps: 29 | //! 30 | //! 1. Creating a style 31 | //! 2. Use this style to "paint" something and get a `Painted` object 32 | //! 33 | //! 34 | //! 1. Creating a style 35 | //! ------------------- 36 | //! To create a style a startpoint is needed: This can either be a startpoint 37 | //! with an attached modifier (like `Red`: modifies the fg-color) or the 38 | //! `Plain` startpoint, which does not modify anything. 39 | //! After that, the startpoint can be modified with modifiers like `bold()` or 40 | //! `fg()`. 41 | //! 42 | //! ``` 43 | //! extern crate term_painter; 44 | //! 45 | //! use term_painter::ToStyle; 46 | //! use term_painter::Color::*; 47 | //! use term_painter::Attr::*; 48 | //! 49 | //! fn main() { 50 | //! let x = 5; 51 | //! 52 | //! // These two are equivalent: nothing is formatted/painted 53 | //! println!("{} | {}", x, Plain.paint(x)); 54 | //! 55 | //! // These two are equivalent, too 56 | //! println!("{} | {}", Red.paint(x), Plain.fg(Red).paint(x)); 57 | //! } 58 | //! ``` 59 | //! You can chain as many modifiers as you want. Every modifier overrides 60 | //! preceding modifier: 61 | //! 62 | //! ``` 63 | //! # use term_painter::Attr::*; 64 | //! # use term_painter::Color::*; 65 | //! # use term_painter::ToStyle; 66 | //! 67 | //! // blue, not red 68 | //! println!("{}", Plain.fg(Red).fg(Blue).paint("Apple")); 69 | //! ``` 70 | //! 71 | //! 2. Use the style 72 | //! ---------------- 73 | //! After building the style, you can use it in two different ways. 74 | //! 75 | //! One way is to call `paint` to use it on some object. 76 | //! `paint` will return the wrapper object `Painted` that holds your object and 77 | //! the specified style. `Painted` implements any formatting trait (like 78 | //! `Display` and `Debug`) if and only if the type of the given Object, `T`, 79 | //! does. So a `Painted` object can be printed via `println!` or similar macros. 80 | //! When it gets printed, it will apply the given style before printing the 81 | //! object of type `T` and will reset the style after printing. 82 | //! 83 | //! `Note`: `paint` will consume the passed object. This is no problem when 84 | //! passing constant literals (like `paint("cheesecake")`) or types that are 85 | //! `Copy`. Otherwise it could be confusing because just printing should not 86 | //! consume a variable. To prevent consuming, just pass a reference to the 87 | //! object (with `&`). Example: 88 | //! 89 | //! ``` 90 | //! extern crate term_painter; 91 | //! 92 | //! use term_painter::ToStyle; 93 | //! use term_painter::Color::*; 94 | //! use term_painter::Attr::*; 95 | //! 96 | //! fn main() { 97 | //! let non_copy = "cake".to_string(); // String is *not* Copy 98 | //! let copy = 27; // i32 *is* Copy 99 | //! 100 | //! println!("{}", Plain.paint(&non_copy)); 101 | //! println!("{}", Plain.paint(©)); 102 | //! // non_copy is still usable here... 103 | //! // copy is still usable here... 104 | //! 105 | //! println!("{}", Plain.paint(non_copy)); 106 | //! println!("{}", Plain.paint(copy)); 107 | //! // non_copy was moved into `paint`, so it not usable anymore... 108 | //! // copy is still usable here... 109 | //! } 110 | //! ``` 111 | //! 112 | //! Another way is to call `with`. `with` takes another function (usually a 113 | //! closure) and everything that is printed within that closure is formatted 114 | //! with the given style. Specifically, `with()` sets the given style, 115 | //! calls the given function and resets the style afterwards. It can be 116 | //! chained and used together with `paint()`. Inner calls will overwrite 117 | //! outer calls of `with`. 118 | //! 119 | //! ``` 120 | //! extern crate term_painter; 121 | //! 122 | //! use term_painter::ToStyle; 123 | //! use term_painter::Color::*; 124 | //! use term_painter::Attr::*; 125 | //! 126 | //! fn main() { 127 | //! Red.with(|| { 128 | //! print!("JustRed"); 129 | //! Bold.with(|| { 130 | //! print!(" BoldRed {} BoldRed ", Underline.paint("Underline")); 131 | //! }); 132 | //! print!("JustRed "); 133 | //! 134 | //! print!("{}", Blue.paint("Blue (overwrite) ")); 135 | //! Green.with(|| { 136 | //! println!("Green (overwrite)"); 137 | //! }); 138 | //! }); 139 | //! } 140 | //! ``` 141 | //! 142 | //! Some Notes 143 | //! ---------- 144 | //! If you don't want to pollute your namespace with `Color` and `Attr` names, 145 | //! you can use a more qualified name (`Color::Red.paint(..)`) and remove these 146 | //! `use` statements: 147 | //! 148 | //! - `use term_painter::Color::*;` 149 | //! - `use term_painter::Attr::*;` 150 | //! 151 | //! Please note that global state is changed when printing a `Painted` 152 | //! object. This means that some state is set before and reset after printing. 153 | //! This means that, for example, using this library in `format!` or `write!` 154 | //! won't work. The color formatting is not stored in the resulting string. 155 | //! Although Unix terminals do modify color and formatting by printing special 156 | //! control characters, Windows and others do not. This library uses the 157 | //! plattform independent library `term`, thus saving formatted text in a 158 | //! string not possible. This was a design choice. 159 | //! 160 | //! This crate also assumes that the terminal state is not altered by anything 161 | //! else. Calling `term` function directly might result in strange behaviour. 162 | //! This is due to the fact that one can not read the current terminal state. 163 | //! In order to work like this, this crate needs to track terminal state 164 | //! itself. However, there shouldn't be any problems when the terminal state 165 | //! is completely reset in between using those two different methods. 166 | //! 167 | //! Another possible source of confusion might be multithreading. Terminal 168 | //! state and handles are hold in thread local variables. If two terminal 169 | //! handles would reference the same physical terminal, those two threads could 170 | //! interfere with each other. I have not tested it, though. Usually, you don't 171 | //! want to print to the same Terminal in two threads simultanously anyway. 172 | //! 173 | //! Functions of `term` sometimes return a `Result` that is `Err` when the 174 | //! function fails to set the state. However, this crate silently ignores those 175 | //! failures. To check the capabilities of the terminal, use `term` directly. 176 | //! 177 | 178 | extern crate term; 179 | 180 | use std::default::Default; 181 | use std::fmt; 182 | use std::cell::RefCell; 183 | 184 | 185 | /// Everything that can be seen as part of a style. This is the core of this 186 | /// crate. All functions ("style modifier") consume self and return a modified 187 | /// version of the style. 188 | pub trait ToStyle : Sized { 189 | fn to_style(self) -> Style; 190 | 191 | /// Convenience method for modifying the style before it's returned. 192 | fn to_mapped_style(self, func: F) -> Style 193 | where F: FnOnce(&mut Style) 194 | { 195 | let mut s = self.to_style(); 196 | func(&mut s); 197 | s 198 | } 199 | 200 | /// Sets the foreground (text) color. 201 | fn fg(self, c: Color) -> Style { 202 | self.to_mapped_style(|s| s.fg = c) 203 | } 204 | 205 | /// Sets the background color. 206 | fn bg(self, c: Color) -> Style { 207 | self.to_mapped_style(|s| s.bg = c) 208 | } 209 | 210 | /// Makes the text bold. 211 | fn bold(self) -> Style { 212 | self.to_mapped_style(|s| s.set_bold(Some(true))) 213 | } 214 | 215 | /// Dim mode. 216 | fn dim(self) -> Style { 217 | self.to_mapped_style(|s| s.set_dim(Some(true))) 218 | } 219 | 220 | /// Underlines the text. 221 | fn underline(self) -> Style { 222 | self.to_mapped_style(|s| s.set_underline(Some(true))) 223 | } 224 | 225 | /// Removes underline-attribute. 226 | fn not_underline(self) -> Style { 227 | self.to_mapped_style(|s| s.set_underline(Some(false))) 228 | } 229 | 230 | /// Underlines the text. 231 | fn blink(self) -> Style { 232 | self.to_mapped_style(|s| s.set_blink(Some(true))) 233 | } 234 | 235 | /// Underlines the text. 236 | fn reverse(self) -> Style { 237 | self.to_mapped_style(|s| s.set_reverse(Some(true))) 238 | } 239 | 240 | /// Secure mode. 241 | fn secure(self) -> Style { 242 | self.to_mapped_style(|s| s.set_secure(Some(true))) 243 | } 244 | 245 | /// Wraps the style specified in `self` and something of arbitrary type 246 | /// into a `Painted`. When `Painted` is printed it will print the arbitrary 247 | /// something with the given style. 248 | fn paint(&self, obj: T) -> Painted 249 | where Self: Clone 250 | { 251 | Painted { 252 | style: self.clone().to_style(), 253 | obj: obj, 254 | } 255 | } 256 | 257 | /// Executes the given function, applying the style information before 258 | /// calling it and resetting after it finished. 259 | fn with(&self, f: F) -> R 260 | where F: FnOnce() -> R, 261 | Self: Clone 262 | { 263 | // Shorthand for the new style and the style that was active before 264 | let new = self.clone().to_style(); 265 | let before = CURR_STYLE.with(|curr| curr.borrow().clone()); 266 | 267 | // Apply the new style and setting the merged style as CURR_STYLE 268 | let _ = new.apply(); 269 | CURR_STYLE.with(|curr| *curr.borrow_mut() = before.and(new)); 270 | 271 | let out = f(); 272 | 273 | // Revert to the style that was active before and set it as current 274 | let _ = before.revert_to(); 275 | CURR_STYLE.with(|curr| *curr.borrow_mut() = before); 276 | 277 | out 278 | } 279 | } 280 | 281 | /// Lists all possible Colors. It implements `ToStyle` so it's possible to call 282 | /// `ToStyle`'s methods directly on a `Color` variant like: 283 | /// 284 | /// ``` 285 | /// # use term_painter::{Attr, Color, ToStyle}; 286 | /// 287 | /// println!("{}", Color::Red.bold().paint("Red and bold")); 288 | /// ``` 289 | /// 290 | /// It is not guaranteed that the local terminal supports all of those colors. 291 | /// As already mentioned in the module documentation, you should use `term` 292 | /// directly to check the terminal's capabilities. 293 | /// 294 | /// **Note**: Using `Color::NotSet` will *not* reset the color to the default 295 | /// terminal color. 296 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 297 | pub enum Color { 298 | NotSet, 299 | Black, 300 | Red, 301 | Green, 302 | Yellow, 303 | Blue, 304 | Magenta, 305 | Cyan, 306 | White, 307 | BrightBlack, 308 | BrightRed, 309 | BrightGreen, 310 | BrightYellow, 311 | BrightBlue, 312 | BrightMagenta, 313 | BrightCyan, 314 | BrightWhite, 315 | Custom(u32), 316 | } 317 | 318 | impl Color { 319 | /// Returns the associated constant from `term::color::Color`. 320 | fn term_constant(&self) -> Option { 321 | match *self { 322 | Color::NotSet => None, 323 | Color::Black => Some(term::color::BLACK), 324 | Color::Red => Some(term::color::RED), 325 | Color::Green => Some(term::color::GREEN), 326 | Color::Yellow => Some(term::color::YELLOW), 327 | Color::Blue => Some(term::color::BLUE), 328 | Color::Magenta => Some(term::color::MAGENTA), 329 | Color::Cyan => Some(term::color::CYAN), 330 | Color::White => Some(term::color::WHITE), 331 | Color::BrightBlack => Some(term::color::BRIGHT_BLACK), 332 | Color::BrightRed => Some(term::color::BRIGHT_RED), 333 | Color::BrightGreen => Some(term::color::BRIGHT_GREEN), 334 | Color::BrightYellow => Some(term::color::BRIGHT_YELLOW), 335 | Color::BrightBlue => Some(term::color::BRIGHT_BLUE), 336 | Color::BrightMagenta => Some(term::color::BRIGHT_MAGENTA), 337 | Color::BrightCyan => Some(term::color::BRIGHT_CYAN), 338 | Color::BrightWhite => Some(term::color::BRIGHT_WHITE), 339 | Color::Custom(c) => Some(c) 340 | } 341 | } 342 | } 343 | 344 | impl Default for Color { 345 | fn default() -> Self { 346 | Color::NotSet 347 | } 348 | } 349 | 350 | impl ToStyle for Color { 351 | /// Returns a Style with default values and the `self` color as foreground 352 | /// color. 353 | fn to_style(self) -> Style { 354 | Style { 355 | fg: self, 356 | .. Style::default() 357 | } 358 | } 359 | } 360 | 361 | /// Lists possible attributes. It implements `ToStyle` so it's possible to call 362 | /// `ToStyle`'s methods directly on a `Attr` variant like: 363 | /// 364 | /// ``` 365 | /// # use term_painter::{Attr, Color, ToStyle}; 366 | /// 367 | /// println!("{}", Attr::Bold.fg(Color::Red).paint("Red and bold")); 368 | /// ``` 369 | /// 370 | /// It is not guaranteed that the local terminal supports all of those 371 | /// formatting options. As already mentioned in the module documentation, you 372 | /// should use `term` directly to check the terminal's capabilities. 373 | /// 374 | /// For more information about enum variants, see `term::Attr` Documentation. 375 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 376 | pub enum Attr { 377 | /// Just default style 378 | Plain, 379 | Bold, 380 | Dim, 381 | Underline, 382 | Blink, 383 | Reverse, 384 | Secure, 385 | } 386 | 387 | impl ToStyle for Attr { 388 | /// Returns a Style with default values and the `self` attribute enabled. 389 | fn to_style(self) -> Style { 390 | let mut s = Style::default(); 391 | match self { 392 | Attr::Plain => {}, 393 | Attr::Bold => s.set_bold(Some(true)), 394 | Attr::Dim => s.set_dim(Some(true)), 395 | Attr::Underline => s.set_underline(Some(true)), 396 | Attr::Blink => s.set_blink(Some(true)), 397 | Attr::Reverse => s.set_reverse(Some(true)), 398 | Attr::Secure => s.set_secure(Some(true)), 399 | } 400 | s 401 | } 402 | } 403 | 404 | /// Saves all properties of a style. Implements `ToStyle`, so you can call 405 | /// style modifiers on it. 406 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 407 | pub struct Style { 408 | pub fg: Color, 409 | pub bg: Color, 410 | // Each attribute was `Option` once. To reduce struct size, the 411 | // Option type is simulated with 2 bits for each attribute. The first 412 | // attribute in the name uses the MSBs, the last attribute the LSBs. 413 | // 00 => None, 10 => Some(false), 11 => Some(true) 414 | bold_dim_underline_blink: u8, 415 | reverse_secure: u8, 416 | } 417 | 418 | 419 | impl Default for Style { 420 | fn default() -> Self { 421 | Style { 422 | fg: Color::default(), 423 | bg: Color::default(), 424 | bold_dim_underline_blink: 0, 425 | reverse_secure: 0, 426 | } 427 | } 428 | } 429 | 430 | thread_local!( 431 | static TERM: RefCell>> = RefCell::new(term::stdout()) 432 | ); 433 | thread_local!( 434 | static CURR_STYLE: RefCell