1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! A terminal-based text editor with goals to maximize simplicity and efficiency.
//!
//! # Design Goals
//! 1) All functionality shall be able to be performed via the keys reachable from the home row. Where it makes sense, functionality may additionally be performed via the mouse and other keys.
//! 2) All user input shall be modal, i.e. keys may implement different functionality depending on the current mode of the application.
//! 3) Paper shall utilize already implemented tools and commands wherever possible; specifically paper shall support the [Language Server Protocol].
//!
//! [Language Server Protocol]: https://microsoft.github.io/language-server-protocol/
#![allow(
    clippy::unreachable, // unreachable added by enum_map::Enum.
    clippy::use_self, // False positive on format macro.
    clippy::trivial_regex, // Trivial regex added by thiserror::Error.
)]

mod app;
mod io;
mod logging;
mod orient;

// Export so that other crates can build Arguments.
pub use logging::LogConfig;

use {
    app::Processor,
    // Avoid use of std Option.
    core::option::Option,
    fehler::{throw, throws},
    io::{
        ConsumeInputError, ConsumeInputIssue, CreateInterfaceError, Interface, ProduceOutputError,
    },
    log::{error, info},
    logging::InitLoggerError,
    market::{Consumer, Producer},
    structopt::StructOpt,
    thiserror::Error as ThisError,
};

/// Arguments for [`Paper`] initialization.
///
/// [`Paper`]: ../struct.Paper.html
#[derive(Clone, Debug, Default, StructOpt)]
pub struct Arguments {
    /// The file to be viewed.
    #[structopt(value_name("FILE"))]
    file: Option<String>,
    #[allow(clippy::missing_docs_in_private_items)] // Flattened structs do not allow doc comments.
    #[structopt(flatten)]
    log_config: LogConfig,
}

/// An instance of the `paper` application.
///
/// Once [`Paper`] has started running, it shall continue until it has been terminated. **Termination** occurs when the application is ordered to quit or an unrecoverable error is thrown.
///
/// When [`Paper`] is dropped, it shall kill all spawned processes and return the user interface to its previous state.
///
/// # Examples
///
/// ```no_run
/// use paper::{Arguments, Failure, Paper};
/// # fn main() -> Result<(), Failure> {
///
/// Paper::new(Arguments::default())?.run()?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug)]
pub struct Paper {
    /// The interface with external processes.
    io: Interface,
    /// The application processor.
    processor: Processor,
}

impl Paper {
    /// Creates a new instance of `paper`.
    #[inline]
    #[throws(CreateError)]
    pub fn new(arguments: Arguments) -> Self {
        // Logger is created first so all other parts can use it.
        logging::init(arguments.log_config)?;

        Self {
            io: Interface::new(arguments.file)?,
            processor: Processor::new(),
        }
    }

    /// Runs the application.
    ///
    /// This function shall run until `paper` has been **terminated**.
    ///
    /// # Errors
    ///
    /// If any unrecoverable error is thrown, a [`RunError`] shall be thrown.
    ///
    /// [`RunError`]: enum.RunError.html
    #[inline]
    #[throws(RunError)]
    pub fn run(&mut self) {
        if let Err(error) = self.execute() {
            error!("{}", error);
            throw!(error);
        }

        info!("Application quitting");
    }

    /// Loops through execution until `paper` has been **terminated**.
    ///
    /// # Errors
    ///
    /// If any unrecoverable error is thrown, a [`RunError`] shall be thrown.
    ///
    /// [`RunError`]: enum.RunError.html
    #[throws(RunError)]
    fn execute(&mut self) {
        loop {
            match self.io.demand() {
                Ok(input) => self.io.force_all(self.processor.process(input))?,
                Err(issue) => {
                    if let ConsumeInputIssue::Error(error) = issue {
                        throw!(error);
                    }

                    break;
                }
            }
        }

        self.io.join();
    }
}

/// An error from which `paper` is unable to recover.
#[derive(Debug, ThisError)]
pub enum Failure {
    /// An error creating `paper`.
    #[error(transparent)]
    Create(#[from] CreateError),
    /// An error running `paper`.
    #[error(transparent)]
    Run(#[from] RunError),
}

/// An error creating a [`Paper`].
///
/// [`Paper`]: struct.Paper.html
#[derive(Debug, ThisError)]
pub enum CreateError {
    /// An error creating the application logger.
    #[error("Failed to initialize logger: {0}")]
    Logger(#[from] InitLoggerError),
    /// An error creating the [`Interface`].
    ///
    /// [`Interface`]: io/struct.Interface.html
    #[error("Failed to create application: {0}")]
    Interface(#[from] CreateInterfaceError),
}

/// An error running `paper`.
#[derive(Debug, ThisError)]
pub enum RunError {
    /// An error consuming an input.
    #[error("Failed to consume input: {0}")]
    Consume(#[from] ConsumeInputError),
    /// An error producing an output.
    #[error("Failed to produce output: {0}")]
    Produce(#[from] ProduceOutputError),
}