├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE
├── Makefile
├── README.md
├── appveyor.yml
├── examples
└── progress.rs
└── src
├── lib.rs
└── os
├── default.rs
├── mod.rs
└── windows.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | Cargo.lock
3 | *.bk
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: rust
3 | matrix:
4 | fast_finish: true
5 | include:
6 | - rust: nightly
7 | - rust: beta
8 | - rust: stable
9 | script:
10 | - cargo build
11 | - cargo test
12 | cache:
13 | apt: true
14 | directories:
15 | - target/debug/deps
16 | - target/debug/build
17 | addons:
18 | apt:
19 | packages:
20 | - libcurl4-openssl-dev
21 | - libelf-dev
22 | - libdw-dev
23 | - binutils-dev
24 | - libbfd-dev
25 | after_success: |-
26 | [ $TRAVIS_RUST_VERSION = stable ] &&
27 | [ $TRAVIS_BRANCH = master ] &&
28 | [ $TRAVIS_PULL_REQUEST = false ] &&
29 | cargo doc --no-deps &&
30 | echo "" > target/doc/index.html &&
31 | pip install --user ghp-import &&
32 | /home/travis/.local/bin/ghp-import -n target/doc &&
33 | git push -fq https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages &&
34 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
35 | tar xzf master.tar.gz && mkdir kcov-master/build && cd kcov-master/build && cmake .. && make && make install DESTDIR=../tmp && cd ../.. &&
36 | ls target/debug &&
37 | ./kcov-master/tmp/usr/local/bin/kcov --coveralls-id=$TRAVIS_JOB_ID --exclude-pattern=/.cargo target/kcov target/debug/screenprints-*
38 | env:
39 | global:
40 | secure: pVnYZUqJua2n7sCJcrw52z4TrYr2BV2Cv1duZJWV12O+5kG/UxBZu3VCXIWfUxoRFthuE2dW5un5ayqzFHcUftrMHUnzntSbb03nifEfS0dTt5baKEAkk5eYA4qQPPNCjUfT8LpHRhF9piTKTycGpHP60/rUWA/enow8rytptOLEAL1W1g60n8YcNl9//AarTC1ybmifYpIFR/FO1E+OiBlVwkimRG/u6UK7TZTyV+2dw7eRbpD/9Vw3KdS+RByfwjHwDbvyKdr99dfzE2eFs23npCyzyVzf5frigIlcKiegWEu/fATEQM8BIb0ASdPy2rKO5UgtrpF4JGU1iG42YViV7oXdw9zvONoN2f40Y6ZMiqBEYLPsJ732wRFE9wJWz6xm7H87+y8etr84Osxv6on1Cj9lizgrD1Q9oCKuG+JzuDVFQx1RqVSh8eBQqIOwNSMK2NC30ZmTpL0LhraBQ8PsqEZMLBiEj7AOBC4vAoUwtGOjmzM44m0uqPXT+iG7lBsDX3I7kt6FBmo+xLtJIw5zvEeovGeewQO5ffsrQG/+/Bzui7hfxoU9M1iqDMZvLY35ZOqMp0ayzpitX4PJ6OVFnuN5lJWUrziEzU6qRy6zjRrviupY1b+nnx4+kcrfRnj/qPrtwYhknTau14ALG1pF1qMEBAM5xZVxfXhJBFU=
41 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.1.1
2 |
3 | * added windows support [via @nabijaczleweli](https://github.com/softprops/screenprints/pull/3)
4 |
5 | # 0.1.0
6 |
7 | Initial release
8 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "screenprints"
3 | # Remember to also update in appveyor.yml
4 | version = "0.1.1"
5 | authors = ["softprops "]
6 | description = "reprints for your terminal screen"
7 | documentation = "https://softprops.github.io/screenprints"
8 | homepage = "https://github.com/softprops/screenprints"
9 | repository = "https://github.com/softprops/screenprints"
10 | keywords = ["terminal", "tui", "console"]
11 | license = "MIT"
12 |
13 | [dependencies]
14 | kernel32-sys = "0.2"
15 | winapi = "0.2"
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Doug Tangren
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | @cargo build
3 |
4 | test:
5 | @cargo test
6 |
7 | example:
8 | @cargo run --example progress
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # screenprints
2 |
3 | [](https://travis-ci.org/softprops/screenprints) [](LICENSE) 
4 |
5 | > reprints for your terminal screen
6 |
7 |
8 | Screenprints acts as a buffer for terminal display continuously printing output at a configured interval.
9 |
10 | ## api docs
11 |
12 | Find them [here](https://softprops.github.io/screenprints)
13 |
14 | ## usage
15 |
16 | Screensprints defines a `Printer` which implements [std::io::Write](https://doc.rust-lang.org/std/io/trait.Write.html) which means anywhere you would normally write output to, you could substitude in an instance of a `Printer`.
17 |
18 | ```rust
19 | extern crate screenprints;
20 |
21 | use screenprints::Printer;
22 | use std::io::{stdout, Write};
23 | use std::time::Duration;
24 | use std::thread;
25 |
26 | fn main() {
27 | let mut printer = Printer::new(stdout(), Duration::from_millis(10));
28 | for f in &["foo.txt", "bar.txt", "baz.txt"] {
29 | for i in 0..51 {
30 | let _ = write!(printer, "Downloading {}.. ({}/{}) GB\n", f, i, 50);
31 | thread::sleep(Duration::from_millis(50));
32 | }
33 | }
34 | }
35 | ```
36 |
37 | The result should look something like the following
38 |
39 | [](https://asciinema.org/a/9auhm32umebr14bulaifhynni)
40 |
41 |
42 | Doug Tangren (softprops) 2016
43 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 0.1.0-{build}
2 |
3 | branches:
4 | except:
5 | - gh-pages
6 |
7 | skip_tags: false
8 |
9 | platform: x64
10 | configuration: Release
11 |
12 | clone_folder: C:\screenprints
13 |
14 | install:
15 | - curl -L https://static.rust-lang.org/dist/rust-beta-x86_64-pc-windows-gnu.msi -oC:\rust-beta-x86_64-pc-windows-gnu.msi
16 | - start /w msiexec /qn /quiet /passive /a C:\rust-beta-x86_64-pc-windows-gnu.msi TARGETDIR="C:\Program Files"
17 | -
18 | - set PATH=C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%;C:\Program Files\Rust\bin;C:\
19 | -
20 | - bash -lc "pacman --needed --noconfirm -Sy pacman-mirrors"
21 | - bash -lc "pacman --noconfirm -Sy"
22 | - bash -lc "pacman --noconfirm -S mingw-w64-x86_64-toolchain"
23 |
24 | build: off
25 | build_script:
26 | - cargo build --verbose
27 |
28 | test: off
29 | test_script:
30 | - cargo test --verbose
31 |
32 | notifications:
33 | - provider: Email
34 | to:
35 | - d.tangren@gmail.com
36 | on_build_success: false
37 |
--------------------------------------------------------------------------------
/examples/progress.rs:
--------------------------------------------------------------------------------
1 | extern crate screenprints;
2 |
3 | use screenprints::Printer;
4 | use std::io::{stdout, Write};
5 | use std::time::Duration;
6 | use std::thread;
7 |
8 | fn main() {
9 | let mut p = Printer::new(stdout(), Duration::from_millis(10));
10 | for f in &["foo.txt", "bar.txt", "baz.txt"] {
11 | for i in 0..51 {
12 | let _ = write!(p, "Downloading {}.. ({}/{}) GB\n", f, i, 50);
13 | thread::sleep(Duration::from_millis(50));
14 | }
15 | // p.clear();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! # Screenprints
2 | //!
3 | //! Screensprints is a terminal interface tool.
4 | use std::io::{Result, Write};
5 | use std::sync::mpsc::{channel, Sender};
6 | use std::sync::{Arc, Mutex};
7 | use std::time::Duration;
8 | use std::thread;
9 |
10 | mod os;
11 |
12 | enum Op {
13 | Write(Vec),
14 | Clear,
15 | Flush,
16 | Close,
17 | }
18 |
19 | /// A Printer buffers writes and flushes at
20 | /// a specified interval, clearing the display of any
21 | /// lines of text previously written
22 | pub struct Printer {
23 | writes: Arc>>,
24 | }
25 |
26 | impl Printer {
27 | /// Creates a new Printer instance that delegates writes to the provided
28 | /// Write instance delayed at the interval provided
29 | pub fn new(mut underlying: W, interval: Duration) -> Printer
30 | where W: Write + Send + 'static
31 | {
32 | // write op signals
33 | let (writer, writes) = channel();
34 | let writers = Arc::new(Mutex::new(writer));
35 | let sleeper = writers.clone();
36 | let forwards = writers.clone();
37 | let printer = Printer { writes: forwards };
38 |
39 | // inter thread close signals
40 | let (closer, closing) = channel();
41 |
42 | thread::spawn(move || {
43 | loop {
44 | if let Ok(_) = closing.try_recv() {
45 | return;
46 | }
47 | thread::sleep(interval);
48 | if let Ok(s) = sleeper.lock() {
49 | let _ = s.send(Op::Flush);
50 | }
51 | }
52 | });
53 |
54 | thread::spawn(move || {
55 | let mut buffer = vec![];
56 | let mut lines = 0;
57 | loop {
58 | if let Ok(op) = writes.recv() {
59 | match op {
60 | Op::Clear => {
61 | buffer.clear();
62 | lines = 0
63 | }
64 | Op::Close => {
65 | let _ = closer.send(());
66 | return;
67 | }
68 | Op::Flush => {
69 | if buffer.is_empty() {
70 | continue;
71 | }
72 | // clear lines
73 | for _ in 0..lines {
74 | os::clear_line_move_one_up(&mut underlying);
75 | }
76 | lines = buffer.iter().filter(|&b| *b == b'\n').count();
77 |
78 | let _ = underlying.write(&buffer);
79 | let _ = underlying.flush();
80 | buffer.clear();
81 | }
82 | Op::Write(data) => buffer.extend(data),
83 | }
84 | }
85 | }
86 | });
87 | printer
88 | }
89 |
90 | /// clear the current buffer and reset linecount
91 | pub fn clear(&self) {
92 | let _ = self.writes.lock().unwrap().send(Op::Clear);
93 | }
94 |
95 | /// close the printer, afterwhich all writes will be discarded
96 | pub fn close(&self) {
97 | let _ = self.writes.lock().unwrap().send(Op::Close);
98 | }
99 | }
100 |
101 | impl Write for Printer {
102 | fn write(&mut self, data: &[u8]) -> Result {
103 | let _ = self.writes.lock().unwrap().send(Op::Write(data.to_owned()));
104 | Ok(data.len())
105 | }
106 |
107 | fn flush(&mut self) -> Result<()> {
108 | let _ = self.writes.lock().unwrap().send(Op::Flush);
109 | Ok(())
110 | }
111 | }
112 |
113 | impl Drop for Printer {
114 | fn drop(&mut self) {
115 | self.close();
116 | }
117 | }
118 |
119 | #[test]
120 | fn it_works() {}
121 |
--------------------------------------------------------------------------------
/src/os/default.rs:
--------------------------------------------------------------------------------
1 | use std::io::Write;
2 |
3 | pub fn clear_line_move_one_up(underlying: &mut W)
4 | where W: Write + Send + 'static
5 | {
6 | let _ = write!(underlying, "\x1B[0A"); // Move the cursor up
7 | let _ = write!(underlying, "\x1B[2K\r"); // Clear the line
8 | }
9 |
--------------------------------------------------------------------------------
/src/os/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(windows)]
2 | mod windows;
3 | #[cfg(windows)]
4 | pub use self::windows::*;
5 |
6 | #[cfg(not(windows))]
7 | mod default;
8 | #[cfg(not(windows))]
9 | pub use self::default::*;
10 |
--------------------------------------------------------------------------------
/src/os/windows.rs:
--------------------------------------------------------------------------------
1 | extern crate winapi;
2 | extern crate kernel32;
3 |
4 | use std::io::Write;
5 |
6 | pub fn clear_line_move_one_up(_: &mut W)
7 | where W: Write + Send + 'static
8 | {
9 | let h_console = unsafe { kernel32::GetStdHandle(winapi::winbase::STD_OUTPUT_HANDLE) };
10 | let mut csbi = winapi::wincon::CONSOLE_SCREEN_BUFFER_INFO {
11 | dwSize: winapi::wincon::COORD { X: 0, Y: 0 },
12 | dwCursorPosition: winapi::wincon::COORD { X: 0, Y: 0 },
13 | wAttributes: 0,
14 | srWindow: winapi::wincon::SMALL_RECT {
15 | Left: 0,
16 | Top: 0,
17 | Right: 0,
18 | Bottom: 0,
19 | },
20 | dwMaximumWindowSize: winapi::wincon::COORD { X: 0, Y: 0 },
21 | };
22 |
23 | unsafe { kernel32::GetConsoleScreenBufferInfo(h_console, &mut csbi) };
24 | let expected_cursor_position = winapi::wincon::COORD {
25 | X: 0,
26 | Y: csbi.dwCursorPosition.Y - 1,
27 | };
28 | unsafe {
29 | kernel32::FillConsoleOutputCharacterA(h_console,
30 | ' ' as winapi::winnt::CHAR,
31 | csbi.dwSize.X as winapi::minwindef::DWORD,
32 | expected_cursor_position,
33 | &mut 0)
34 | };
35 |
36 | unsafe { kernel32::GetConsoleScreenBufferInfo(h_console, &mut csbi) };
37 | unsafe {
38 | kernel32::FillConsoleOutputAttribute(h_console,
39 | csbi.wAttributes,
40 | csbi.dwSize.X as winapi::minwindef::DWORD,
41 | expected_cursor_position,
42 | &mut 0)
43 | };
44 |
45 | unsafe { kernel32::SetConsoleCursorPosition(h_console, expected_cursor_position) };
46 | }
47 |
--------------------------------------------------------------------------------