lib.rs 3.15 KB
Newer Older
Cyril Plisko's avatar
Cyril Plisko committed
1 2 3
//! Does all the magic to have you potentially long output piped through the
//! external pager. Similar to what git does for its output.
//!
Cyril Plisko's avatar
Cyril Plisko committed
4
//! # Quick Start
Cyril Plisko's avatar
Cyril Plisko committed
5
//!
Cyril Plisko's avatar
Cyril Plisko committed
6
//! ```rust
Cyril Plisko's avatar
Cyril Plisko committed
7
//! extern crate pager;
Cyril Plisko's avatar
Cyril Plisko committed
8
//! use pager::Pager;
Cyril Plisko's avatar
Cyril Plisko committed
9 10 11 12 13 14 15 16
//! fn main() {
//!     Pager::new().setup();
//!     // The rest of your program goes here
//! }
//! ```
//!
//! Under the hood this forks the current process, connects child' stdout
//! to parent's stdin, and then replaces the parent with the pager of choice
Cyril Plisko's avatar
Cyril Plisko committed
17 18 19
//! (environment variable PAGER). The child just continues as normal. If PAGER
//! environment variable is not present `Pager` probes current PATH for `more`.
//! If found it is used as a default pager.
Cyril Plisko's avatar
Cyril Plisko committed
20 21 22 23
//!
//! You can control pager to a limited degree. For example you can change the
//! environment variable used for finding pager executable.
//!
Cyril Plisko's avatar
Cyril Plisko committed
24
//! ```rust
Cyril Plisko's avatar
Cyril Plisko committed
25
//! extern crate pager;
Cyril Plisko's avatar
Cyril Plisko committed
26
//! use pager::Pager;
Cyril Plisko's avatar
Cyril Plisko committed
27
//! fn main() {
28
//!     Pager::env("MY_PAGER").setup();
Cyril Plisko's avatar
Cyril Plisko committed
29 30 31 32
//!     // The rest of your program goes here
//! }
//! ```
//!
Cyril Plisko's avatar
Cyril Plisko committed
33
//! If no suitable pager found `setup()` does nothing and your executable keeps
Cyril Plisko's avatar
Cyril Plisko committed
34 35 36
//! running as usual. `Pager` cleans after itself and doesn't leak resources in
//! case of setup failure.
//!
Cyril Plisko's avatar
Cyril Plisko committed
37
//! If you need to disable pager altogether set environment variable `NOPAGER` and `Pager::setup()`
38
//! will skip initialization. The host application will continue as normal. `Pager::is_on()` will
Cyril Plisko's avatar
Cyril Plisko committed
39
//! reflect the fact that no Pager is active.
Cyril Plisko's avatar
Cyril Plisko committed
40

Cyril Plisko's avatar
Cyril Plisko committed
41
extern crate errno;
Cyril Plisko's avatar
Cyril Plisko committed
42 43
extern crate libc;

Cyril Plisko's avatar
Cyril Plisko committed
44
mod utils;
45

Cyril Plisko's avatar
Cyril Plisko committed
46
use std::ffi::OsString;
Cyril Plisko's avatar
Cyril Plisko committed
47

48
const DEFAULT_PAGER_ENV: &str = "PAGER";
Cyril Plisko's avatar
Cyril Plisko committed
49 50 51

#[derive(Debug, Default)]
pub struct Pager {
Cyril Plisko's avatar
Cyril Plisko committed
52
    pager: Option<OsString>,
Cyril Plisko's avatar
Cyril Plisko committed
53
    env: Option<String>,
54
    on: bool,
Cyril Plisko's avatar
Cyril Plisko committed
55 56 57
}

impl Pager {
Cyril Plisko's avatar
Cyril Plisko committed
58
    /// Creates new instance of pager with default settings
Cyril Plisko's avatar
Cyril Plisko committed
59
    pub fn new() -> Self {
60
        Pager::env(DEFAULT_PAGER_ENV)
Cyril Plisko's avatar
Cyril Plisko committed
61 62
    }

Cyril Plisko's avatar
Cyril Plisko committed
63
    /// Creates new instance of pager using `env` environment variable instead of PAGER
64
    pub fn env(env: &str) -> Self {
Cyril Plisko's avatar
Cyril Plisko committed
65
        let pager = utils::find_pager(env);
Cyril Plisko's avatar
Cyril Plisko committed
66

67
        Pager {
Cyril Plisko's avatar
Cyril Plisko committed
68
            pager: pager,
Cyril Plisko's avatar
Cyril Plisko committed
69
            env: String::from(env).into(),
70
            on: true,
71
        }
Cyril Plisko's avatar
Cyril Plisko committed
72 73
    }

Cyril Plisko's avatar
Cyril Plisko committed
74
    /// Gives quick assessment of successful Pager setup
75 76
    pub fn is_on(&self) -> bool {
        self.on
Cyril Plisko's avatar
Cyril Plisko committed
77 78
    }

Cyril Plisko's avatar
Cyril Plisko committed
79 80
    /// Initiates Pager framework and sets up all the necessary environment for sending standard
    /// output to the activated pager.
Cyril Plisko's avatar
Cyril Plisko committed
81
    pub fn setup(&mut self) {
Cyril Plisko's avatar
Cyril Plisko committed
82
        if let Some(ref pager) = self.pager {
Cyril Plisko's avatar
Cyril Plisko committed
83 84
            let (pager_stdin, main_stdout) = utils::pipe();
            let pid = utils::fork();
Cyril Plisko's avatar
Cyril Plisko committed
85 86 87
            match pid {
                -1 => {
                    // Fork failed
Cyril Plisko's avatar
Cyril Plisko committed
88 89
                    utils::close(pager_stdin);
                    utils::close(main_stdout);
90
                    self.on = false
Cyril Plisko's avatar
Cyril Plisko committed
91 92 93
                }
                0 => {
                    // I am child
Cyril Plisko's avatar
Cyril Plisko committed
94 95
                    utils::dup2(main_stdout, libc::STDOUT_FILENO);
                    utils::close(pager_stdin);
Cyril Plisko's avatar
Cyril Plisko committed
96 97 98
                }
                _ => {
                    // I am parent
Cyril Plisko's avatar
Cyril Plisko committed
99 100
                    utils::dup2(pager_stdin, libc::STDIN_FILENO);
                    utils::close(main_stdout);
101
                    utils::execvp(pager);
Cyril Plisko's avatar
Cyril Plisko committed
102 103 104 105 106
                }
            }
        }
    }
}