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
//! Types and functions that are shared between platform-specific
//! implementations of Polyhorn CLI commands.

use ansi_term::Colour::{Cyan, Fixed, Green};
use indicatif::{ProgressBar, ProgressStyle};

pub mod cargo;

/// Represents an individual task that a CLI command is composed of.
pub trait Task {
    /// The type of context that is passed to this task, processed and
    /// subsequently returned by this task.
    type Context;

    /// The type of error that this task can return.
    type Error;

    /// The verb that describes this task (e.g. "Launching" or "Building") that
    /// is shown to the user while the task is running.
    fn verb(&self) -> &str;

    /// The message that is shown to the user alongside the verb. This usually
    /// starts with a lowercase letter (e.g. "[Generating] source tree").
    fn message(&self) -> &str;

    /// Optional additional text that is shown to the user alongside the
    /// message. This usually starts with a lowercase letter too (e.g.
    // "[Generating] [source tree] for Android").
    fn detail(&self) -> &str;

    /// This function should execute the task.
    fn run(
        &self,
        context: Self::Context,
        manager: &mut Manager,
    ) -> Result<Self::Context, Self::Error>;
}

/// Manager that can provide additional utilities (e.g. progress tracking) to
/// tasks.
pub struct Manager<'a> {
    verb: &'a str,
    message: &'a str,
    detail: &'a str,
}

impl<'a> Manager<'a> {
    /// Creates a new manager for the given task.
    pub fn new<T>(task: &'a T) -> Manager<'a>
    where
        T: Task + ?Sized,
    {
        eprint!(
            "{} {} {}",
            Cyan.bold().paint(format!("{:>12}", task.verb())),
            task.message(),
            Fixed(8).paint(task.detail())
        );

        Manager {
            verb: task.verb(),
            message: task.message(),
            detail: task.detail(),
        }
    }

    /// Returns a progress bar for the task that corresponds to this manager.
    pub fn progress_bar(&mut self, len: usize) -> ProgressBar {
        let bar = ProgressBar::new(len as u64);

        eprint!("\r");

        bar.set_style(
            ProgressStyle::default_bar()
                .template(&format!(
                    "{} [{{bar:57}}] {{pos:>{}}}/{{len}}: {} {}",
                    Cyan.bold().paint(format!("{:>12}", self.verb)),
                    1000.0f32.log10().ceil() as usize,
                    self.message,
                    Fixed(8).paint(self.detail)
                ))
                .progress_chars("=> "),
        );

        bar
    }
}

impl<'a> Drop for Manager<'a> {
    fn drop(&mut self) {
        eprintln!(
            "\r{} {} {} {}",
            Green.bold().paint(format!("    Finished")),
            self.verb,
            self.message,
            Fixed(8).paint(self.detail)
        );
    }
}

/// Executioner that manes the execution of a sequence of a tasks.
pub struct Executioner;

impl Executioner {
    /// Executes the given sequence of tasks with the given initial context. The
    /// first task receives the initial context. Each subsequent task receives
    /// the input from the previous task. This function will return the
    /// resulting context of the last task.
    pub fn execute<T>(tasks: &[T], context: T::Context) -> Result<T::Context, T::Error>
    where
        T: Task,
    {
        let mut context = context;

        for task in tasks {
            let mut manager = Manager::new(task);
            context = task.run(context, &mut manager)?;
        }

        Ok(context)
    }
}