├── .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 | [![Build Status](https://travis-ci.org/softprops/screenprints.svg?branch=master)](https://travis-ci.org/softprops/screenprints) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) ![crates.io](http://meritbadge.herokuapp.com/screenprints) 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 | [![asciicast](https://asciinema.org/a/9auhm32umebr14bulaifhynni.png)](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 | --------------------------------------------------------------------------------