Skip to content

Commit f439c09

Browse files
authored
Merge pull request #26 from code-troopers/fix/18
Fix/18
2 parents d8c5a91 + 44b941a commit f439c09

File tree

6 files changed

+206
-100
lines changed

6 files changed

+206
-100
lines changed

.ctproject

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ build="cargo build --release" #build the release binary
33
help=cat .ctproject
44
dummy=echo "toto is dumb"
55
ragdoll=echo 'tata is a ragdoll'
6-
combine=echo "toto is dumb" && echo "tata is a ragdoll" # no recursive ct invoke for this test sentence
6+
combine=echo "toto is dumb" && echo "tata is a ragdoll" # no recursive ct invoke for this test sentence
7+
envvar='CTENV="azerty qsdf";echo $CTENV'
8+
# comment on line
9+
front='cd ui; ct run' #debugging #18
10+
dual="ct help && ct help"

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ct"
3-
version = "0.1.0"
3+
version = "0.1.2"
44
authors = ["Cedric Gatay <[email protected]>"]
55

66
[dependencies]
@@ -18,4 +18,5 @@ serde_json = "1.0"
1818
rocket = "0.3.11"
1919
rocket_codegen = "0.3.11"
2020
linked-hash-map = "0.5.1"
21-
lazy_static = "1.0"
21+
lazy_static = "1.0"
22+
clap = "2.0.0"

src/extract.rs

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
extern crate regex;
21
extern crate linked_hash_map;
3-
use self::regex::Regex;
4-
use self::linked_hash_map::*;
5-
use std::process::*;
2+
extern crate regex;
3+
64
use cli::Config;
75
use file_finder::CTFile;
86
use log::debug_log;
7+
use self::linked_hash_map::*;
8+
use self::regex::Regex;
9+
use std::process::*;
910

1011
#[derive(Debug)]
1112
pub struct RunCommand {
@@ -15,21 +16,31 @@ pub struct RunCommand {
1516
}
1617

1718
impl RunCommand{
18-
pub fn all<'a>(file_content: &'a str, config: Option<&Config>) -> LinkedHashMap<String, RunCommand>{
19-
let regex = Regex::new(r"(?m)^\s*([^=]*)=([^#\n]*)(#\s*(.*)\s*)?$").unwrap();
19+
pub fn all<'a>(file_content: &'a str, config: &Option<Config>) -> LinkedHashMap<String, RunCommand>{
20+
let regex = Regex::new(r#"(?m)^\s*([^#=]*)=([^#\n]*)(#\s*(.*)\s*)?$"#).unwrap();
2021
let mut commands: LinkedHashMap<String, RunCommand> = LinkedHashMap::new();
2122
for capture in regex.captures_iter(file_content){
2223
let alias = &capture[1];
23-
let command_with_args = &capture[2].replace("\"", "").replace("'", "");
24+
debug_log(|| format!(" Handling alias : {}", alias) );
25+
debug_log(|| format!(" Raw captured command : {}", &capture[2]) );
26+
let extracted_command = &capture[2].trim();
27+
let chars = extracted_command.chars().collect::<Vec<char>>();
28+
let len = chars.len();
29+
let command_with_args: &str;
30+
if (chars[0] == '\'' || chars[0] == '"') && chars[len - 1] == chars[0] {
31+
command_with_args = extracted_command.trim_matches(chars[0]);
32+
}else{
33+
command_with_args = extracted_command;
34+
}
35+
debug_log(|| format!(" Cleaned command {}", command_with_args) );
2436

2537
let doc = capture.get(4).map(|m| m.as_str()).map(ToString::to_string).unwrap_or(String::from(""));
38+
//this is probably useless since we're running it with sh -c (and probably invalid as first split might not match command if var are exported at beginning of line
2639
let commands_vec: Vec<_> = command_with_args.split(" ").collect();
2740
let (command, args) = commands_vec.split_first().unwrap();
2841

2942
let mut args_as_vect: Vec<String> = args.iter().map(|s| s.to_string()).collect();
30-
if config.is_some() {
31-
args_as_vect.append(&mut config.unwrap().args.clone());
32-
}
43+
args_as_vect.append(config.as_ref().map(|c| c.args.clone()).unwrap_or(vec![]).as_mut());
3344
args_as_vect = args_as_vect.into_iter().filter(|a| { a.len() > 0 }).collect();
3445

3546
commands.insert(alias.to_string(), RunCommand{command: command.to_string(), args: args_as_vect, doc});;
@@ -38,19 +49,25 @@ impl RunCommand{
3849
}
3950

4051
pub fn run(&self, ct_file: &CTFile){
41-
let mut sh_sub_command = Vec::new();
42-
sh_sub_command.push(self.command.to_string());
43-
sh_sub_command.push(String::from(" "));
44-
sh_sub_command.push(self.args.join(" ")); // no need to escape "', it is properly handled
45-
debug_log(|| format!("About to run `sh -c {:?}`", sh_sub_command.join("")));
52+
let sh_sub_command = self.build_subcommand();
53+
debug_log(|| format!("About to run `sh -c {:?}`", sh_sub_command));
4654
let s = Command::new("sh")
55+
.env("CTNOBANNER","true")
4756
.arg("-c")
48-
.arg(sh_sub_command.join(""))
57+
.arg(sh_sub_command)
4958
.current_dir(ct_file.path.clone())
5059
.spawn().unwrap();
5160
//result printed to stdout / stderr as expected as io are shared
5261
let _output = s.wait_with_output();
5362
}
63+
64+
fn build_subcommand(&self) -> String {
65+
let mut sh_sub_command = Vec::new();
66+
sh_sub_command.push(self.command.to_string());
67+
sh_sub_command.push(String::from(" "));
68+
sh_sub_command.push(self.args.join(" ")); // no need to escape "', it is properly handled
69+
sh_sub_command.join("")
70+
}
5471
}
5572

5673
#[cfg(test)]
@@ -60,7 +77,7 @@ mod tests{
6077
#[test]
6178
fn it_should_extract_single_quoted_command(){
6279
let config = Config::new(vec!["ct", "command"].into_iter().map(ToString::to_string).collect()).unwrap();
63-
let map = RunCommand::all("command='run'", Some(&config));
80+
let map = RunCommand::all("command='run'", &Some(config));
6481
let run_command = map.get("command").unwrap();
6582
assert_eq!(run_command.command, "run");
6683
assert_eq!(run_command.args.join(" "), "");
@@ -69,7 +86,7 @@ mod tests{
6986
#[test]
7087
fn it_should_extract_double_quoted_command(){
7188
let config = Config::new(vec!["ct", "command"].into_iter().map(ToString::to_string).collect()).unwrap();
72-
let map = RunCommand::all("command=\"run\"", Some(&config));
89+
let map = RunCommand::all("command=\"run\"", &Some(config));
7390
let run_command = map.get("command").unwrap();
7491
assert_eq!(run_command.command, "run");
7592
assert_eq!(run_command.args.join(" "), "");
@@ -78,7 +95,7 @@ mod tests{
7895
#[test]
7996
fn it_should_extract_not_quoted_command(){
8097
let config = Config::new(vec!["ct", "command"].into_iter().map(ToString::to_string).collect()).unwrap();
81-
let map = RunCommand::all("command=run", Some(&config));
98+
let map = RunCommand::all("command=run", &Some(config));
8299
let run_command = map.get("command").unwrap();
83100
assert_eq!(run_command.command, "run");
84101
assert_eq!(run_command.args.join(" "), "");
@@ -87,7 +104,7 @@ mod tests{
87104
#[test]
88105
fn it_should_append_args_to_run_command_if_no_args_in_run_command(){
89106
let config = Config::new(vec!["ct", "command", "arg1", "arg2"].into_iter().map(ToString::to_string).collect()).unwrap();
90-
let map = RunCommand::all("command=run", Some(&config));
107+
let map = RunCommand::all("command=run", &Some(config));
91108
let run_command = map.get("command").unwrap();
92109
assert_eq!(run_command.command, "run");
93110
assert_eq!(run_command.args.join(" "), "arg1 arg2");
@@ -96,7 +113,7 @@ mod tests{
96113
#[test]
97114
fn it_should_append_args_to_run_command_if_args_in_run_command(){
98115
let config = Config::new(vec!["ct", "command", "arg1", "arg2"].into_iter().map(ToString::to_string).collect()).unwrap();
99-
let map = RunCommand::all("command=run tests", Some(&config));
116+
let map = RunCommand::all("command=run tests", &Some(config));
100117
let run_command = map.get("command").unwrap();
101118
assert_eq!(run_command.command, "run");
102119
assert_eq!(run_command.args.join(" "), "tests arg1 arg2");
@@ -107,7 +124,7 @@ mod tests{
107124
let map = RunCommand::all(r"command=run tests
108125
command2=run app
109126
command3=push commits
110-
", None);
127+
", &None);
111128
assert_eq!(map.len(), 3);
112129
assert_eq!(map.contains_key("command"), true);
113130
assert_eq!(map.contains_key("command2"), true);
@@ -116,14 +133,14 @@ mod tests{
116133

117134
#[test]
118135
fn it_should_match_command_with_leading_spaces(){
119-
let map = RunCommand::all(" command=run tests", None);
136+
let map = RunCommand::all(" command=run tests", &None);
120137
assert_eq!(map.len(), 1);
121138
assert_eq!(map.contains_key("command"), true);
122139
}
123140

124141
#[test]
125142
fn it_should_match_command_with_doc(){
126-
let map = RunCommand::all("command=run tests # this run tests", None);
143+
let map = RunCommand::all("command=run tests # this run tests", &None);
127144
assert_eq!(map.len(), 1);
128145
let run_command = map.get("command").unwrap();
129146
assert_eq!(run_command.command, "run");
@@ -134,17 +151,33 @@ mod tests{
134151

135152
#[test]
136153
fn it_should_match_command_with_leading_tab(){
137-
let map = RunCommand::all("\tcommand=run tests", None);
154+
let map = RunCommand::all("\tcommand=run tests", &None);
155+
assert_eq!(map.len(), 1);
156+
assert_eq!(map.contains_key("command"), true);
157+
}
158+
159+
#[test]
160+
fn it_should_remove_surrounding_single_quotes(){
161+
let map = RunCommand::all("command='run tests'", &None);
162+
assert_eq!(map.len(), 1);
163+
assert_eq!(map.contains_key("command"), true);
164+
assert_eq!(map.get("command").unwrap().command, "run");
165+
}
166+
167+
#[test]
168+
fn it_should_keep_quotes_on_exports(){
169+
let map = RunCommand::all(r#"command='VAR="toto tutu";run tests'"#, &None);
138170
assert_eq!(map.len(), 1);
139171
assert_eq!(map.contains_key("command"), true);
172+
assert_eq!(map.get("command").unwrap().build_subcommand(), r#"VAR="toto tutu";run tests"#);
140173
}
141174

142175

143176
#[test]
144177
#[should_panic]
145178
fn it_should_error_if_line_does_not_match_pattern(){
146179
let config = Config::new(vec!["ct", "command"].into_iter().map(ToString::to_string).collect()).unwrap();
147-
let map = RunCommand::all("command", Some(&config));
180+
let map = RunCommand::all("command", &Some(config));
148181
let _run_command = map.get("command").unwrap();
149182
}
150183
}

src/file_finder.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::io::Read;
33
use std::env::current_dir;
44
use std::path::PathBuf;
55
use std::result::Result::Err;
6+
use log::debug_log;
67

78
const FILE_NAME: &str = ".ctproject";
89

@@ -30,7 +31,7 @@ impl CTFile{
3031
let mut current_dir = current_dir().unwrap();
3132
let mut file_path = current_dir.join(FILE_NAME);
3233
while !file_path.exists() && current_dir.pop() {
33-
println!("Did not find in {:?}, looking in directory {:?}", file_path, current_dir);
34+
debug_log(|| format!("Did not find in {:?}, looking in directory {:?}", file_path, current_dir));
3435
file_path = current_dir.join(FILE_NAME);
3536
}
3637
if !file_path.exists(){
@@ -47,6 +48,5 @@ impl CTFile{
4748
ctfile.read_to_string(&mut contents)
4849
.expect("something went wrong reading the file");
4950
contents
50-
//self.line = contents.clone().lines()
5151
}
5252
}

src/lib.rs

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
11
#![feature(plugin, extern_prelude)]
22
#![plugin(rocket_codegen)]
3+
extern crate colored;
4+
#[macro_use]
5+
extern crate lazy_static;
36
extern crate rocket;
4-
5-
67
#[macro_use]
78
extern crate serde_derive;
8-
#[macro_use]
9-
extern crate lazy_static;
109

1110

12-
use cli::Config;
11+
use colored::*;
1312
use file_finder::CTFile;
13+
use man::CTMan;
14+
use ports::CTPorts;
15+
use rocket::http::ContentType;
16+
use rocket::response::Content;
1417
use std::env;
18+
use std::error::Error;
19+
use std::fs::File;
20+
use std::io::Write;
21+
use std::path::Path;
1522

1623
pub mod cli;
1724
pub mod extract;
@@ -24,27 +31,57 @@ pub mod banner;
2431
#[macro_use]
2532
pub mod ports_html;
2633

27-
use rocket::response::Content;
28-
use rocket::http::ContentType;
29-
use ports::CTPorts;
30-
31-
pub fn find_command(config: &Config, ct_file: &CTFile) -> String{
32-
println!("{}", ct_file.content);
33-
let matching_line: String = ct_file.content.lines()
34-
.filter(|line| line.starts_with(&config.command))
35-
.last()
36-
//build a "fake" command with the one the user tries to execute
37-
.map_or(format!("{}={}", &config.command, &config.command), |v| { v.to_string() });
38-
matching_line
39-
}
40-
4134
pub fn show_banner(){
4235
let show_banner = env::var("CTNOBANNER").unwrap_or("false".to_string());
4336
if show_banner == "false" {
4437
println!("{}", BANNER!());
4538
}
4639
}
4740

41+
pub fn init_project(){
42+
let path = Path::new(".ctproject");
43+
let display = path.display();
44+
45+
// Open a file in write-only mode, returns `io::Result<File>`
46+
if path.exists(){
47+
println!("⚠️ .ctproject already exists");
48+
return;
49+
}
50+
let mut file = match File::create(&path) {
51+
Err(why) => panic!("❌ couldn't create {}: {}",
52+
display,
53+
why.description()),
54+
Ok(file) => file,
55+
};
56+
57+
match file.write_all("run='your run command'\nbuild='your build command'\ntest='your test command'".as_bytes()) {
58+
Err(why) => {
59+
panic!("❌ couldn't write to {}: {}", display,
60+
why.description())
61+
},
62+
Ok(_) => println!("{} successfully wrote to {}", "✔︎".green(), display),
63+
}
64+
}
65+
66+
67+
pub fn start_port_listening() {
68+
println!("👂 Started ports web server at http://localhost:1500, CTRL+C to exit...");
69+
start_rocket();
70+
}
71+
72+
pub fn show_man(man_entry: Option<&str>, ct_file: Option<CTFile>) {
73+
if let Some(ct_file) = ct_file {
74+
if let Some(ct_man) = CTMan::all(&ct_file) {
75+
if man_entry.is_some() {
76+
if let Some(man) = ct_man.get(man_entry.unwrap()) {
77+
man.print();
78+
}
79+
} else {
80+
ct_man.values().for_each(CTMan::print);
81+
}
82+
}
83+
}
84+
}
4885

4986
#[get("/scan")]
5087
fn scan() -> String {

0 commit comments

Comments
 (0)