eureka/
lib.rs

1extern crate dirs;
2#[macro_use]
3extern crate log;
4extern crate core;
5
6use std::io;
7use std::io::{Error, ErrorKind};
8
9use crate::config_manager::ConfigManagement;
10use crate::config_manager::ConfigType::Repo;
11use crate::git::GitManagement;
12use crate::printer::{Print, PrintColor};
13use crate::program_access::ProgramOpener;
14use crate::reader::ReadInput;
15use std::path::Path;
16
17pub mod config_manager;
18pub mod git;
19pub mod printer;
20pub mod program_access;
21pub mod reader;
22
23pub struct Eureka<
24    CM: ConfigManagement,
25    W: Print + PrintColor,
26    R: ReadInput,
27    G: GitManagement,
28    PO: ProgramOpener,
29> {
30    cm: CM,
31    printer: W,
32    reader: R,
33    git: G,
34    program_opener: PO,
35}
36
37#[derive(Debug)]
38pub struct EurekaOptions {
39    // Clear the stored config
40    pub clear_config: bool,
41
42    // Open idea document with $PAGER (fall back to `less`)
43    pub view: bool,
44}
45
46impl<CM, W, R, G, PO> Eureka<CM, W, R, G, PO>
47where
48    CM: ConfigManagement,
49    W: Print + PrintColor,
50    R: ReadInput,
51    G: GitManagement,
52    PO: ProgramOpener,
53{
54    pub fn new(cm: CM, printer: W, reader: R, git: G, program_opener: PO) -> Self {
55        Eureka {
56            cm,
57            printer,
58            reader,
59            git,
60            program_opener,
61        }
62    }
63
64    pub fn run(&mut self, opts: EurekaOptions) -> io::Result<()> {
65        debug!("Running with options: {:?}", &opts);
66
67        if opts.clear_config {
68            self.clear_config()?;
69            debug!("Cleared config");
70            return Ok(());
71        }
72
73        if opts.view {
74            self.open_idea_file()?;
75            return Ok(());
76        }
77
78        if self.is_config_missing() {
79            debug!("Config is missing");
80
81            // If config dir is missing - create it
82            if !self.cm.config_dir_exists() {
83                self.cm.config_dir_create()?;
84                debug!("Created config dir");
85            }
86
87            self.printer.fts_banner()?;
88
89            // If repo path is missing - ask for it
90            if self.cm.config_read(Repo).is_err() {
91                self.setup_repo_path()?;
92                debug!("Setup repo path successfully");
93            }
94
95            self.printer
96                .println("First time setup complete. Happy ideation!")?;
97            Ok(())
98        } else {
99            self.ask_for_idea()
100        }
101    }
102
103    fn ask_for_idea(&mut self) -> io::Result<()> {
104        let mut idea_summary = String::new();
105
106        while idea_summary.is_empty() {
107            self.printer.input_header(">> Idea summary")?;
108            idea_summary = self.reader.read_input()?;
109        }
110
111        let repo_path = self.cm.config_read(Repo)?;
112        // We can set initialize git now as we have the repo path
113        self.git
114            .init(&repo_path)
115            .map_err(|git_err| Error::new(ErrorKind::InvalidInput, git_err))?;
116
117        self.program_opener
118            .open_editor(&format!("{}/README.md", &repo_path))
119            .and(self.git_add_commit_push(idea_summary))
120    }
121
122    fn clear_config(&self) -> io::Result<()> {
123        self.cm.config_rm()
124    }
125
126    fn open_idea_file(&self) -> io::Result<()> {
127        self.program_opener
128            .open_pager(&format!("{}/README.md", self.cm.config_read(Repo)?))
129    }
130
131    fn git_add_commit_push(&mut self, commit_subject: String) -> io::Result<()> {
132        let branch_name = "main";
133        self.printer.println(&format!(
134            "Adding and committing your new idea to {}..",
135            &branch_name
136        ))?;
137        self.git
138            .checkout_branch(branch_name)
139            .and_then(|_| self.git.add())
140            .and_then(|_| self.git.commit(commit_subject.as_str()))
141            .map_err(|err| io::Error::new(ErrorKind::Other, err))?;
142        self.printer.println("Added and committed!")?;
143
144        self.printer.println("Pushing your new idea..")?;
145        self.git
146            .push(branch_name)
147            .map_err(|err| io::Error::new(ErrorKind::Other, err))?;
148        self.printer.println("Pushed!")?;
149
150        Ok(())
151    }
152
153    fn setup_repo_path(&mut self) -> io::Result<()> {
154        loop {
155            self.printer
156                .input_header("Absolute path to your idea repo")?;
157            let user_input = &self.reader.read_input()?;
158
159            if user_input.is_empty() {
160                continue;
161            }
162
163            let path = Path::new(user_input);
164
165            if path.is_absolute() {
166                break self.cm.config_write(Repo, path.display().to_string());
167            } else {
168                self.printer.error("Path must be absolute")?;
169            }
170        }
171    }
172
173    fn is_config_missing(&self) -> bool {
174        self.cm.config_read(Repo).is_err()
175    }
176}