rusty_x/
x.rs

1use error::Error;
2use error::Error::InternalError;
3use git;
4use project;
5use snippet;
6
7use std::process::Command;
8
9use std::env;
10use std::fs;
11use std::fs::File;
12use std::io;
13use std::io::Write;
14use std::path;
15
16#[derive(Debug)]
17pub enum OpCode<'a> {
18    // For the new snippet command
19    NewSnippet(&'a project::SnippetLocation),
20    // For the add snippet command
21    AddSnippet(String, &'a project::SnippetLocation),
22    // For listing snippets
23    ListSnippets(bool),
24    // For syncing snippets with the server
25    PullSnippets,
26    // Save snippets to repo
27    SaveSnippets,
28}
29
30/// Find the snippets associated with the project
31pub fn find_snippets(project: &project::Project) -> Result<Vec<fs::DirEntry>, Error> {
32    // Crawl through directory that is set as project root
33    let mut res: Vec<fs::DirEntry> = Vec::new();
34
35    // Read the entries in the folder
36    for snippet_location in project.locations.iter() {
37        println!("Finding snippets in {},", &snippet_location.local.as_str());
38        let mut entries: Vec<fs::DirEntry> = fs::read_dir(&snippet_location.local)?
39            .filter_map(|x| x.ok())
40            .collect();
41
42        // For each of the entries
43        let mut entries: Vec<_> = entries
44            .into_iter()
45            .filter_map(|e| {
46                let dir_ent = e;
47
48                // Get the path
49                let path = dir_ent.path();
50                // Get the extension
51                let ext_opt = path.extension();
52                if let Some(ext) = ext_opt {
53                    if let Some(s) = ext.to_str() {
54                        // Add to list if files match extension
55                        if s == snippet_location.ext {
56                            return Some(dir_ent);
57                        }
58                    }
59                }
60                return None;
61            })
62            .collect();
63        res.append(&mut entries);
64    }
65    Ok(res)
66}
67
68/// Load snippets from the dir entries
69pub fn load_snippets(
70    dir_entries: &Vec<fs::DirEntry>,
71    keywords: &Vec<String>,
72) -> Result<Vec<snippet::Snippet>, Error> {
73    let keyword_slice = keywords.as_slice();
74
75    // Get all tags for entries
76    let mut tag_with_entries: Vec<(u32, &fs::DirEntry, Vec<String>)> = Vec::new();
77    for entry in dir_entries {
78        // Read the tags
79        let tags = snippet::read_tags(entry.path().to_str().unwrap())?;
80
81        // If tag is in the snippet, or no tags are given
82        // Filter which don't contain the keyword
83        let tag_count : u32 = tags.iter()
84            .fold(0, |x, tag| x + if keyword_slice.contains(tag) { 1 } else { 0 });
85        if keyword_slice.is_empty() || tag_count > 0 {
86            tag_with_entries.push((tag_count, entry, snippet::read_tags(entry.path().to_str().unwrap())?));
87        }
88    }
89
90    // Sort by number of matched tags
91    tag_with_entries.sort_by(|a, b| b.0.cmp(&a.0) );
92
93    // This maps the files and tags, to a snippet
94    let result = tag_with_entries
95        .iter()
96        .map(|(count, entry, tags)| {
97            snippet::Snippet::new(entry.path().to_str().unwrap().to_string(), &tags)
98        })
99        .collect();
100
101    Ok(result)
102}
103
104//// Edit snippets
105pub fn edit_snippet(program: &str, full_path: &path::Path) -> Result<(), Error> {
106    let final_editor = default_editor(program);
107    let _output = Command::new(final_editor)
108        .arg(&full_path)
109        .spawn()?
110        .wait_with_output()?;
111
112    Ok(())
113}
114
115/// New snippet
116pub fn new_snippet(program: &str, working_dir: &path::Path) -> Result<(), Error> {
117    let final_editor = default_editor(program);
118
119    let _output = Command::new(final_editor)
120        .current_dir(&working_dir)
121        .spawn()?
122        .wait_with_output()?;
123
124    Ok(())
125}
126
127fn default_editor(program: &str) -> String {
128    let final_editor: String;
129    if let Ok(editor) = env::var("EDITOR") {
130        final_editor = editor.into();
131    } else {
132        final_editor = program.into()
133    };
134    final_editor
135}
136
137//// Start the different operation modes
138pub fn start_operation(
139    code: &OpCode,
140    project: &project::Project,
141    keywords: Vec<String>,
142) -> Result<Vec<snippet::Snippet>, Error> {
143    // Match on operation
144    let result = match code {
145        OpCode::AddSnippet(new_file, location) => {
146            // Create the full path
147            let full_path = path::Path::new(&location.local).join(new_file);
148            // Create the file
149            if full_path.exists() {
150                return Err(InternalError("Snippet already exists".to_string()));
151            }
152            let mut file = File::create(&full_path)?;
153
154            // Write the keywords to the file
155            for keyword in &keywords {
156                file.write(keyword.as_bytes())?;
157                file.write(b",")?;
158            }
159
160            // Open vim on location
161            edit_snippet("vim", &full_path)?;
162
163            let snippet =
164                snippet::Snippet::new(full_path.into_os_string().into_string().unwrap(), &keywords);
165            Ok(vec![snippet])
166        }
167
168        // Add a new snippet
169        OpCode::NewSnippet(location) => {
170            let path = path::Path::new(&location.local);
171
172            new_snippet("vim", path)?;
173            Ok(vec![])
174        }
175
176        // List snippets
177        OpCode::ListSnippets(_) => {
178            let files = find_snippets(&project)?;
179            let snippets = load_snippets(&files, &keywords)?;
180
181            Ok(snippets)
182        }
183
184        // Sync snippets
185        OpCode::PullSnippets => {
186            println!("Pulling snippet locations...");
187            for location in &project.locations {
188                // Only sync if it is a git location
189                if location.git == Some(true) {
190                    git::git_pull(location)?;
191                }
192            }
193            Ok(vec![])
194        }
195
196        // Sync snippets
197        OpCode::SaveSnippets => {
198            println!("Saving snippets...");
199            for location in &project.locations {
200                // Only sync if it is a git location
201                if location.git == Some(true) {
202                    git::determine_git_modified_status(location).and_then(|s| {
203                        if let git::GitStatus::Modified = s {
204                            println!("Enter your commit message: ");
205                            let mut msg = String::new();
206                            io::stdin().read_line(&mut msg)?;
207                            // Add
208                            git::git_add(location)?;
209                            // Commit
210                            git::git_commit(location, msg)?;
211                            // Push
212                            git::git_push(location)?;
213                            Ok(())
214                        } else {
215                            // Push to make sure for unpushed changes, TODO change this later to use rev-parse
216                            git::git_push(location)?;
217
218                            Ok(())
219                        }
220                    })?;
221                };
222            }
223            Ok(vec![])
224        }
225    };
226    result
227}