Skip to content

Commit 8b3fad9

Browse files
authored
fix extract command method (#11)
* fix extract command, add test * consider .PHONY: * extract_commandのバグ修正 * fix test * module devide, refactor * fix preview bug * move
1 parent d0e98ec commit 8b3fad9

File tree

5 files changed

+227
-105
lines changed

5 files changed

+227
-105
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ edition = "2021"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9+
regex = "1.7.1"
910
skim = "0.10.4"

Makefile

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
.PHONY: run build check
1+
.PHONY: test build check echo
22

3-
run:
4-
@cargo run
3+
echo:
4+
@echo good
5+
6+
test :
7+
echo "test"
8+
9+
# run:
10+
# @cargo run
511

612
build:
713
@cargo build
814

915
check:
1016
@cargo check
11-
12-
echo:
13-
@echo good

src/main.rs

Lines changed: 9 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
use skim::prelude::*;
2+
use std::process;
23

3-
use std::{fs::File, io::Cursor, io::Read, process};
4+
mod misc;
45

5-
// TODO: add README
66
fn main() {
7-
let (options, items) = get_params();
8-
7+
let (options, items) = misc::get_params();
98
if let output @ Some(_) = Skim::run_with(&options, items) {
109
if output.as_ref().unwrap().is_abort {
1110
process::exit(0)
@@ -14,109 +13,20 @@ fn main() {
1413
let selected_items = output
1514
.map(|out| out.selected_items)
1615
.unwrap_or_else(Vec::new);
16+
1717
for item in selected_items.iter() {
18-
let output = process::Command::new("make")
18+
match process::Command::new("make")
1919
.arg(item.output().to_string())
20-
.output();
21-
match output {
20+
.output()
21+
{
2222
Ok(output) => {
23+
// TODO: extract as function?
2324
println!("\n");
2425
println!("{}", String::from_utf8_lossy(&output.stdout));
2526
println!("{}", String::from_utf8_lossy(&output.stderr));
2627
}
27-
Err(_) => print_error("fail to execute make command".to_string()),
28-
}
29-
}
30-
}
31-
}
32-
33-
fn get_params<'a>() -> (SkimOptions<'a>, Option<Receiver<Arc<dyn SkimItem>>>) {
34-
// TODO: use cat when bat is unavailable
35-
let preview_command = "bat --style=numbers --color=always --highlight-line $(bat Makefile | grep -n {}: | sed -e 's/:.*//g') Makefile";
36-
// TODO: hide fzf window when fzf-make terminated
37-
let options = SkimOptionsBuilder::default()
38-
.height(Some("50%"))
39-
.multi(true)
40-
.preview(Some(preview_command))
41-
.reverse(true)
42-
.build()
43-
.unwrap();
44-
let commands = match extract_command_from_makefile() {
45-
Ok(s) => s,
46-
Err(e) => {
47-
print_error(e.to_string());
48-
process::exit(1)
49-
}
50-
};
51-
let item_reader = SkimItemReader::default();
52-
let items = item_reader.of_bufread(Cursor::new(commands));
53-
54-
(options, Some(items))
55-
}
56-
57-
/// Makefileからcommandを抽出
58-
fn extract_command_from_makefile() -> Result<String, &'static str> {
59-
let mut file = match File::open("Makefile") {
60-
Ok(f) => f,
61-
Err(_) => return Err("Makefile not found"),
62-
};
63-
64-
let mut contents = String::new();
65-
if file.read_to_string(&mut contents).is_err() {
66-
return Err("fail to read Makefile contents");
67-
}
68-
69-
let commands = extract_command(contents);
70-
71-
if !commands.is_empty() {
72-
Ok(commands.join("\n"))
73-
} else {
74-
Err("no make command found")
75-
}
76-
}
77-
78-
fn print_error(error_message: String) {
79-
println!("[ERR] {}", error_message);
80-
}
81-
82-
fn extract_command(contents: String) -> Vec<String> {
83-
let mut result: Vec<String> = Vec::new();
84-
for line in contents.lines() {
85-
if let Some(t) = line.split_once(':') {
86-
if t.0.contains(".PHONY") {
87-
continue;
28+
Err(_) => misc::print_error("fail to execute make command".to_string()),
8829
}
89-
result.push(t.0.to_string());
9030
}
9131
}
92-
93-
result
94-
}
95-
96-
#[cfg(test)]
97-
mod test {
98-
use super::*;
99-
100-
#[test]
101-
fn extract_command_test() {
102-
let contents = "\
103-
.PHONY: run build check
104-
105-
run:
106-
@cargo run
107-
108-
build:
109-
@cargo build
110-
111-
check:
112-
@cargo check
113-
114-
echo:
115-
@echo good";
116-
117-
assert_eq!(
118-
vec!["run", "build", "check", "echo"],
119-
extract_command(contents.to_string())
120-
);
121-
}
12232
}

src/misc.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
use regex::Regex;
2+
use skim::prelude::*;
3+
use std::{
4+
fs::File,
5+
io::{Cursor, Read},
6+
process,
7+
sync::Arc,
8+
};
9+
10+
// FIXME rename module
11+
12+
pub fn print_error(error_message: String) {
13+
println!("[ERR] {}", error_message);
14+
}
15+
16+
pub fn get_params<'a>() -> (SkimOptions<'a>, Option<Receiver<Arc<dyn SkimItem>>>) {
17+
// TODO: use cat when bat is unavailable
18+
let preview_command = "bat --style=numbers --color=always --highlight-line $(bat Makefile | grep -nE '^{}' | sed -e 's/:.*//g') Makefile";
19+
// TODO: hide fzf window when fzf-make terminated
20+
let options = SkimOptionsBuilder::default()
21+
.height(Some("50%"))
22+
.multi(true)
23+
.preview(Some(preview_command))
24+
.reverse(true)
25+
.build()
26+
.unwrap();
27+
let commands = match extract_command_from_makefile() {
28+
// TODO: use some method
29+
Ok(s) => s,
30+
Err(e) => {
31+
print_error(e.to_string());
32+
process::exit(1)
33+
}
34+
};
35+
let item_reader = SkimItemReader::default();
36+
let items = item_reader.of_bufread(Cursor::new(commands));
37+
38+
(options, Some(items))
39+
}
40+
41+
pub fn extract_command_from_makefile() -> Result<String, &'static str> {
42+
let mut file = read_makefile()?;
43+
let contents = read_file_contents(&mut file)?;
44+
let commands = contents_to_commands(contents)?;
45+
Ok(commands.join("\n"))
46+
}
47+
48+
fn read_makefile() -> Result<File, &'static str> {
49+
File::open("Makefile").map_err(|_| "Makefile not found")
50+
}
51+
52+
fn read_file_contents(file: &mut File) -> Result<String, &'static str> {
53+
let mut contents = String::new();
54+
file.read_to_string(&mut contents)
55+
.map(|_| contents)
56+
.map_err(|_| "fail to read Makefile contents")
57+
}
58+
59+
fn contents_to_commands(contents: String) -> Result<Vec<String>, &'static str> {
60+
let mut result: Vec<String> = Vec::new();
61+
for line in contents.lines() {
62+
if let Some(c) = line_to_command(line.to_string()) {
63+
result.push(c);
64+
}
65+
}
66+
67+
if !result.is_empty() {
68+
Ok(result)
69+
} else {
70+
Err("make command not found")
71+
}
72+
}
73+
74+
fn line_to_command(line: String) -> Option<String> {
75+
let regex = Regex::new(r"^[^.#\s].+:$").unwrap();
76+
regex.find(line.as_str()).and_then(|m| {
77+
Some(
78+
m.as_str()
79+
.to_string()
80+
.split_once(':')
81+
.unwrap()
82+
.0
83+
.trim()
84+
.to_string(),
85+
)
86+
})
87+
}
88+
89+
#[cfg(test)]
90+
mod test {
91+
use std::str::FromStr;
92+
93+
use super::*;
94+
95+
#[test]
96+
fn contents_to_commands_test() {
97+
struct Case {
98+
contents: &'static str,
99+
expect: Result<Vec<&'static str>, &'static str>, // NOTE: order of elements of `expect` order should be same as vec function returns
100+
}
101+
let cases = vec![
102+
Case {
103+
contents: "\
104+
.PHONY: run build check
105+
106+
run:
107+
@cargo run
108+
109+
build:
110+
@cargo build
111+
112+
check:
113+
@cargo check
114+
115+
echo:
116+
@echo good",
117+
expect: Ok(vec!["run", "build", "check", "echo"]),
118+
},
119+
Case {
120+
contents: "\
121+
.PHONY: clone build
122+
123+
# https://example.com
124+
clone:
125+
@git clone https://example.com
126+
127+
build:
128+
@cargo build",
129+
expect: Ok(vec!["clone", "build"]),
130+
},
131+
Case {
132+
contents: "echo hello",
133+
expect: Err("make command not found"),
134+
},
135+
];
136+
137+
for case in cases {
138+
let a = case.expect.map(|x| {
139+
x.iter()
140+
.map(|y| String::from_str(y).unwrap())
141+
.collect::<Vec<String>>()
142+
});
143+
assert_eq!(a, contents_to_commands(case.contents.to_string()));
144+
}
145+
}
146+
147+
#[test]
148+
fn extract_command_test() {
149+
struct Case {
150+
contents: &'static str,
151+
expect: Option<&'static str>,
152+
}
153+
let cases = vec![
154+
Case {
155+
contents: "echo:",
156+
expect: Some("echo"),
157+
},
158+
Case {
159+
contents: "main.o:",
160+
expect: Some("main.o"),
161+
},
162+
Case {
163+
contents: "test::",
164+
expect: Some("test"),
165+
},
166+
Case {
167+
contents: "test ::",
168+
expect: Some("test"),
169+
},
170+
Case {
171+
contents: "echo",
172+
expect: None,
173+
},
174+
Case {
175+
contents: " @git clone https://example.com",
176+
expect: None,
177+
},
178+
Case {
179+
contents: ".PHONY:",
180+
expect: None,
181+
},
182+
Case {
183+
contents: ".DEFAULT:",
184+
expect: None,
185+
},
186+
Case {
187+
contents: "https://example.com",
188+
expect: None,
189+
},
190+
Case {
191+
contents: "# run:",
192+
expect: None,
193+
},
194+
Case {
195+
contents: " # run:",
196+
expect: None,
197+
},
198+
];
199+
200+
for case in cases {
201+
assert_eq!(
202+
case.expect.map(|e| e.to_string()),
203+
line_to_command(case.contents.to_string())
204+
);
205+
}
206+
}
207+
}

0 commit comments

Comments
 (0)