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
//! Create and define the main configuration through toml files and command line args

pub mod components;
pub mod styling;

use components::*;

use crate::common::Result;
use async_std::fs;
use async_std::os::unix::io::AsRawFd;
use async_std::sync::Arc;
use serde::Deserialize;
use std::fs::File;
use std::io::Read;

use crate::terminal_size::terminal_size;

#[derive(Debug, Default)]
pub struct Args {
    // flags
    pub full_screen: bool,
    pub inline: bool,
    pub preserve_order: bool,

    // options
    pub lines: Option<usize>,
    pub config: Option<String>,
    pub search: Option<String>,
    pub pool: Option<usize>,
}

/// Arc version of Cfg
pub type Config = Arc<Cfg>;

/// Main configuration structure
#[derive(Deserialize, Clone, Debug, Default)]
pub struct Cfg {
    #[serde(default)]
    pub screen: ScreenConfig,
    #[serde(default)]
    pub initial_query: Option<String>,
    #[serde(default)]
    pub preserve_order: bool,

    #[serde(default)]
    pub prompt: PromptConfig,
    #[serde(default)]
    pub gauge: GaugeConfig,
    #[serde(default)]
    pub candidate: CandidateConfig,
    #[serde(default)]
    pub selection: SelectionConfig,

    #[serde(default)]
    pub advanced: AdvancedConfig,
}

/// Configuration constructor
#[derive(Debug, Default)]
pub struct Configurator {
    config: Option<Cfg>,
}

impl Configurator {
    pub fn new() -> Self {
        Self {
            config: Some(Cfg::default()),
        }
    }

    /// Read configuration from default `$HOME/.config/scout.toml` file
    pub fn from_default_file(&mut self) -> &mut Self {
        if let Some(home) = dirs::home_dir() {
            let file_path = home.join(".config/scout.toml");
            let file_path = file_path.to_str();
            if let Some(path) = file_path {
                match self.read_file(path) {
                    Ok(contents) => {
                        self.from_toml(&contents);
                    }
                    Err(_) => log::trace!("Failed to load contents from $HOME/.config/scout.toml"),
                };
            };
        };

        self
    }

    /// Read configuration from the given path
    pub fn from_file<'a>(&'a mut self, file_path: &str) -> &'a mut Self {
        match self.read_file(file_path) {
            Ok(contents) => self.from_toml(&contents),
            Err(err) => panic!("Failed to read file {:?}. Error {:?}", file_path, err),
        }
    }

    /// Parse toml configuration
    pub fn from_toml<'a>(&'a mut self, contents: &str) -> &'a mut Self {
        self.config = toml::from_str(contents).ok();
        self
    }

    /// Set screen configuration size from PTTY
    pub fn from_ptty<'a>(&'a mut self, ptty: &fs::File) -> &'a mut Self {
        if let Some(mut config) = self.config.take() {
            let (cols, rows) =
                terminal_size(ptty.as_raw_fd()).expect("Error getting terminal size");
            config.screen.set_full_size(cols as usize, rows as usize);

            self.config = Some(config);
        }

        self
    }

    /// Set configuration options from command line args
    pub fn from_args<'a>(&'a mut self, args: &Args) -> &'a mut Self {
        if let Some(mut config) = self.config.take() {
            if args.full_screen {
                config.screen.full_mode();
            }

            if args.inline {
                config.screen.inline_mode();
                if let Some(given) = args.lines {
                    config.screen.set_height(given);
                }
            }

            if args.preserve_order {
                config.preserve_order = true;
            }

            if let Some(q) = &args.search {
                let q = q.to_string();
                config.initial_query = Some(q);
            }

            if let Some(pool) = args.pool {
                config.advanced.set_pool_size(pool);
            }

            self.config = Some(config);
        }

        self
    }

    /// Generate the final Config instance
    pub fn build(&mut self) -> Config {
        match self.config.take() {
            Some(config) => Arc::new(config),
            None => Default::default(),
        }
    }

    fn read_file(&self, file_path: &str) -> Result<String> {
        let mut file = File::open(file_path)?;
        let mut contents = String::new();
        file.read_to_string(&mut contents)?;

        Ok(contents)
    }
}