Compare commits

11 Commits

Author SHA1 Message Date
cf6a9961d1 autocomplete fix ? 2023-11-16 16:46:28 +01:00
165f4a3a08 Autocomplete fix attempt 2023-11-16 15:32:42 +01:00
a56dc578de Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
Added build badge
2023-07-14 12:25:54 +02:00
48322b25e5 drone error correction
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-14 12:21:16 +02:00
fbf6be2779 drone
All checks were successful
continuous-integration/drone/push Build is passing
2023-07-14 12:15:36 +02:00
0aabd711a3 Merge pull request 'dev' (#4) from dev into main
Reviewed-on: #4
2023-05-01 13:44:36 +02:00
f333069a32 Fixed the ./ bug 2023-05-01 13:42:56 +02:00
7677adfafe Readme 2023-03-14 22:27:43 +01:00
1eefd44a89 Merge pull request 'dev' (#3) from dev into main
Reviewed-on: #3
2023-03-04 20:26:00 +01:00
09cea688b7 Added export command 2023-03-04 20:17:22 +01:00
5743678e23 Fixed bug on incorrect command, missing carriage return on cd 2023-03-04 19:56:50 +01:00
5 changed files with 356 additions and 33 deletions

View File

@ -3,8 +3,38 @@ type: docker
name: default name: default
steps: steps:
- name: build
- name: test image: rust:latest
image: rust:1.66.0-alpine3.17
commands: commands:
- cargo build --verbose - cargo build --release
---
kind: pipeline
type: docker
name: release
steps:
- name: build
image: rust:latest
commands:
- cargo build --release
when:
event:
include:
- tag
- name: create-release
image: plugins/gitea-release:latest
custom_dns: [ 9.9.9.9 ]
settings:
api_key:
from_secret: gitea_api
base_url: https://gitea.squi.fr
note: This is an automated release of sqish.
files:
- ./target/release/sqish
when:
event:
include:
- tag

View File

@ -1,4 +1,5 @@
# Sqish # Sqish
[![Build Status](http://drone.sq.lan/api/badges/Rust/sqish/status.svg)](http://drone.sq.lan/Rust/sqish)
## Sqish's goal ## Sqish's goal
Squi's Shell, aka Sqish. This is an attempt to create a simple but usable shell in Rust, because why not. The goal (for me) is also to get more familiar with Rust since I read the Rust book and want to try things out. And, at last, building and using your own tools is pretty cool. Squi's Shell, aka Sqish. This is an attempt to create a simple but usable shell in Rust, because why not. The goal (for me) is also to get more familiar with Rust since I read the Rust book and want to try things out. And, at last, building and using your own tools is pretty cool.
@ -18,14 +19,12 @@ Also keep in mind I'm a beginner in Rust, so the code is pretty dirty; I will pr
TODO (Reformat): TODO (Reformat):
* Creating a struct "shell" that contains builder method taking an input and an output as parameters as well as a conf could be cool * Creating a struct "shell" that contains builder method taking an input and an output as parameters as well as a conf could be cool
* Maybe subdivide lib.src to have more modularity 'cause it getting kinda long... 500 lines it a bit much ?
TODO(Features): TODO (features):
* Allow redirecting >> to the end of a file ? * Add a &&
* Allow && in commands ?
* Improve word jumping TODO (Bugz):
* Add arguments : run a file (sqish myexec) which I believe would make it compatible with gdb ? * Autocomplete shits itself when typing a path starting with /
* Add export command => Reuse the function set_envvars
## sqishrc (Config) ## sqishrc (Config)
See the included sqishrc file included for comments, and copy it as ~/.sqishrc for use (optionnal). See the included sqishrc file included for comments, and copy it as ~/.sqishrc for use (optionnal).

View File

@ -73,6 +73,21 @@ pub mod shell {
} }
} }
fn export_var(command: String) -> Result<CmdOutput, CmdOutput> {
let cmd_split = command.split('=').collect::<Vec<&str>>();
if cmd_split.len() < 2 {
return Err(CmdOutput::from_values(
String::from("Please a format such as : VAR=value"),
255));
}
let key = String::from(cmd_split[0].to_uppercase());
let val = String::from(cmd_split[1]);
let mut map = HashMap::new();
map.insert(key, val);
set_envvars(&map);
return Ok(CmdOutput::new_empty());
}
fn change_dir(args: &Vec<String>, previous_command: &mut Option<Stdio>) -> Result<CmdOutput, CmdOutput> { fn change_dir(args: &Vec<String>, previous_command: &mut Option<Stdio>) -> Result<CmdOutput, CmdOutput> {
let homedir = dirs::home_dir().unwrap().into_os_string().into_string().unwrap(); let homedir = dirs::home_dir().unwrap().into_os_string().into_string().unwrap();
//let new_dir = args.peekable().peek().map_or(homedir.as_str(), |x| *x); //let new_dir = args.peekable().peek().map_or(homedir.as_str(), |x| *x);
@ -127,17 +142,25 @@ pub mod shell {
return Err(CmdOutput::from_values(format!("Could not parse command {:?}", e), -1)); return Err(CmdOutput::from_values(format!("Could not parse command {:?}", e), -1));
}, },
}; };
if parts.len() < 1 {
return Err(CmdOutput::from_values(String::from("Could not parse command"), -1));
}
let command = parts[0].as_str(); let command = parts[0].as_str();
let args = Vec::from(&parts[1..]); let args = Vec::from(&parts[1..]);
match command { match command {
"cd" => { "cd" => {
change_dir(&args, &mut previous_command)?; change_dir(&args, &mut previous_command)?;
print!("\r\n");
}, },
"history" => { "history" => {
let next = commands.peek().is_some(); let next = commands.peek().is_some();
print_hist(&mut previous_command, next)?; print_hist(&mut previous_command, next)?;
}, },
"export" => {
export_var(args.join(" "))?;
print!("\r\n");
},
command => { command => {
if commands.peek().is_some() { if commands.peek().is_some() {
let stdin = match previous_command { let stdin = match previous_command {
@ -385,24 +408,24 @@ pub mod shell {
}; };
} }
fn find_next_space(current_pos: &usize, max_pos: &usize, command: &String) -> usize { fn find_next_space(current_pos: &usize, max_pos: &usize, command: &String) -> usize {
let markers = vec![' ', '!', ',', ';', ':'];
let mut steps: usize = 0; let mut steps: usize = 0;
if *current_pos == *max_pos { return steps; } if *current_pos == *max_pos { return steps; }
for letter in command.chars().skip(*current_pos) { for letter in command.chars().skip(*current_pos+1) {
//println!("L : {:?}, S : {:?} C: {:?} M {:?}", letter, steps, current_pos, max_pos); if markers.contains(&letter) {
//Skip if we start on a space if *current_pos + steps < *max_pos { steps += 1; }
if steps == 0 && (letter == ' ' || letter == ',' || letter == ';') {
continue;
} else if letter != ' ' && *current_pos + steps < *max_pos {
steps += 1;
} else {
return steps; return steps;
} else {
steps += 1;
} }
} }
let max: usize = (0 + max_pos) - current_pos; let max: usize = (0 + max_pos) - current_pos;
return max; return max;
} }
fn handle_conf(stdout: &mut Stdout) -> SqishConf { fn handle_conf(stdout: &mut Stdout) -> SqishConf {
let conf = match SqishConf::from_sqishrc() { let conf = match SqishConf::from_sqishrc() {
Ok(c) => c, Ok(c) => c,
@ -591,7 +614,7 @@ pub mod shell {
fn jmp_prev_word(elems: &mut TermElements) { fn jmp_prev_word(elems: &mut TermElements) {
let command_rev = elems.command.chars().rev().collect::<String>(); let command_rev = elems.command.chars().rev().collect::<String>();
let curr_rev = elems.max_pos - elems.cur_pos + 1; let curr_rev = elems.max_pos - elems.cur_pos;
let steps = find_next_space(&curr_rev, &elems.max_pos, &command_rev); let steps = find_next_space(&curr_rev, &elems.max_pos, &command_rev);
if steps > 0 { if steps > 0 {
print!("{}", cursor::Left(steps as u16)); print!("{}", cursor::Left(steps as u16));
@ -639,8 +662,8 @@ pub mod shell {
} }
//I smell horrible code in here //I smell horrible code in here
fn set_envvars(conf: &SqishConf) { fn set_envvars(vars: &HashMap<String, String>) {
let vars = conf.env.clone(); let vars = vars.clone();
let re = Regex::new(r"\$[A-Za-z_]+").unwrap(); let re = Regex::new(r"\$[A-Za-z_]+").unwrap();
for (key, value) in vars { for (key, value) in vars {
@ -692,7 +715,7 @@ pub mod shell {
//Initializing //Initializing
write!(elems.stdout, "\r\n ---Sqish initializing--- \r\n{}", elems.conf.promptline); write!(elems.stdout, "\r\n ---Sqish initializing--- \r\n{}", elems.conf.promptline);
elems.stdout.flush(); elems.stdout.flush();
set_envvars(&elems.conf); set_envvars(&elems.conf.env);
if elems.conf.init != String::from("") { if elems.conf.init != String::from("") {
run_cmd(&mut String::from(&elems.conf.init), &mut elems.current_number, &mut elems.conf, &mut elems.stdout); run_cmd(&mut String::from(&elems.conf.init), &mut elems.current_number, &mut elems.conf, &mut elems.stdout);

View File

@ -25,6 +25,8 @@ pub struct Search {
local_search: bool, local_search: bool,
//the kind of search //the kind of search
search_type: SearchType, search_type: SearchType,
//the original command
original: String,
} }
impl Search { impl Search {
@ -83,6 +85,7 @@ impl Search {
searchee: searchee, searchee: searchee,
local_search: local, local_search: local,
search_type: search_type, search_type: search_type,
original: String::from(input),
}; };
return Ok(s); return Ok(s);
} }
@ -94,14 +97,13 @@ impl Search {
SearchType::CmdSearch => autocomplete_cmd(&self.searchee, &self), SearchType::CmdSearch => autocomplete_cmd(&self.searchee, &self),
SearchType::FileSearch => match autocomplete_file(&self) { SearchType::FileSearch => match autocomplete_file(&self) {
Ok(t) => t, Ok(t) => t,
Err(_) => (format!("{}{}", &self.command, &self.searchee), String::new()), Err(_) => (String::new(), String::new()),
} }
}; };
if res.len() < 1 { if res.len() < 1 {
*&mut res = format!("{}{}", &self.command, &self.searchee); *&mut res = format!("{}{}", &self.command, &self.searchee);
} }
return (res, res_lines); return (res, res_lines);
} }
@ -141,6 +143,8 @@ fn autocomplete_file(search: &Search) -> std::io::Result<(String, String)> {
let mut last_found = String::new(); let mut last_found = String::new();
let mut results = Vec::new(); let mut results = Vec::new();
//This is pretty messy.
for file in fs::read_dir(search.search_path.clone())? { for file in fs::read_dir(search.search_path.clone())? {
let filepath = file.unwrap().path(); let filepath = file.unwrap().path();
let filename = filepath let filename = filepath
@ -192,18 +196,49 @@ fn autocomplete_file(search: &Search) -> std::io::Result<(String, String)> {
*&mut last_found = search.searchee.clone(); *&mut last_found = search.searchee.clone();
} }
//Handle the path when tabbing in a long path, zsh-like //Not looking for anything in particular, this is a special case.
//then return the value if search.searchee == "" {
let xxx = &search.search_path.clone().into_os_string().into_string().unwrap(); return Ok((String::from(&search.original), all_res));
let mut return_val = String::new();
if !&search.local_search && !&last_found.contains(&xxx.as_str()) {
*&mut return_val = format!("{}{}{}", &search.command, &search.search_path.display(), last_found);
} else {
*&mut return_val = format!("{}{}", &search.command, last_found);
} }
//Remote search... is annoying
if !search.local_search {
last_found = last_found.replace("./", "");
//println!("{:?}", format!("{}{}", search.command, last_found));
if nbr_found < 2 {
return Ok((format!("{}{}{}", search.command, search.search_path.display(), last_found), String::new()));
} else {
return Ok((format!("{}{}", search.command, last_found), all_res));
}
}
last_found = last_found.replace("./", "");
//Otherwise... Handling the return as gracefully as I can
let mut return_val = String::new();
if all_res.len() > 0 {
let ret_string = replace_last_word(&String::from(&search.original), last_found);
*&mut return_val = format!("{}", ret_string);
} else {
*&mut return_val = format!("{}", &search.original);
}
return Ok((return_val, all_res)); return Ok((return_val, all_res));
} }
//Matches on space or slash
fn replace_last_word(sentence: &String, replacement: String) -> String {
let lastword = sentence.split(|c| c == ' ' || c == '/').last().unwrap_or("");
let mut work_sent = sentence.clone();
work_sent.truncate(sentence.len() - lastword.len());
work_sent.push_str(&replacement);
return work_sent;
}
//fn return_last_word(sentence: &String) -> String {
// let lastword = sentence.split(|c| c == ' ' || c == '/').last().unwrap_or("");
// return String::from(lastword);
//}
fn autocomplete_cmd(input: &String, search: &Search) -> (String, String) { fn autocomplete_cmd(input: &String, search: &Search) -> (String, String) {
let found_bins = find_bin(input); let found_bins = find_bin(input);

236
src/shell/commandrun.rs Normal file
View File

@ -0,0 +1,236 @@
use std::io::Stdout;
use std::process::Command;
use std::io::stdin;
use std::io::{stdout};
use std::io::Write;
use std::path::Path;
use std::env;
use std::str;
use std::fs::File;
use std::process::Stdio;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use termion::raw::RawTerminal;
use termion::{color, cursor};
use termion;
use unicode_segmentation::UnicodeSegmentation;
use std::collections::HashMap;
mod history;
use crate::shell::history::*;
///Used to unify command output and error handling
///across functions
pub struct CmdOutput {
pub outp: String,
pub rc: i16,
}
impl CmdOutput {
pub fn from_values(output: String, rc: i16) -> CmdOutput {
let myoutp = CmdOutput {
outp: output,
rc: rc,
};
return myoutp;
}
pub fn new_empty() -> CmdOutput {
let outp = CmdOutput {
outp: String::new(),
rc: 0,
};
return outp;
}
}
fn change_dir(args: &Vec<String>, previous_command: &mut Option<Stdio>) -> Result<CmdOutput, CmdOutput> {
let homedir = dirs::home_dir().unwrap().into_os_string().into_string().unwrap();
//let new_dir = args.peekable().peek().map_or(homedir.as_str(), |x| *x);
let mut new_dir = String::new();
if args.len() > 0 {
*&mut new_dir = args.join(" ");
} else {
*&mut new_dir = String::from(homedir.as_str());
}
let root = Path::new(&new_dir);
if let Err(e) = env::set_current_dir(&root) {
return Err(CmdOutput::from_values(
format!("Error setting directory to {}, got {:?}",
new_dir, e), -1));
};
*previous_command = None;
return Ok(CmdOutput::new_empty());
}
fn print_hist(previous_command: &mut Option<Stdio>, command_next: bool) -> Result<CmdOutput, CmdOutput> {
//let stdout = Stdio::inherit();
let res = get_history();
match res {
Ok(r) => {
let filepath = dirs::home_dir().unwrap().join(".history.sqish");
let file = File::open(filepath).unwrap();
*previous_command = Some(Stdio::from(file));
//if !commands.peek().is_some() {
if !command_next {
print!("{}", r);
}
},
Err(e) => {
return Err(CmdOutput::from_values(format!("Could not print history, got {:?}", e), 255));
},
}
return Ok(CmdOutput::new_empty());
}
fn process_line(input: &str) -> Result<CmdOutput, CmdOutput> {
let mut commands = input.trim().split("|").peekable();
let mut previous_command = None;
let mut resultat = String::new();
let mut rc: i16 = 0;
while let Some(command) = commands.next() {
let parts = match shell_words::split(&command.trim()) {
Ok(w) => w,
Err(e) => {
return Err(CmdOutput::from_values(format!("Could not parse command {:?}", e), -1));
},
};
let command = parts[0].as_str();
let args = Vec::from(&parts[1..]);
match command {
"cd" => {
change_dir(&args, &mut previous_command)?;
},
"history" => {
let next = commands.peek().is_some();
print_hist(&mut previous_command, next)?;
},
command => {
if commands.peek().is_some() {
let stdin = match previous_command {
None => Stdio::inherit(),
Some(o) => o,
};
let stdout = Stdio::piped();
let output = Command::new(command)
.args(args)
.stdout(stdout)
.stdin(stdin)
.spawn();
match output {
Ok(o) => previous_command = Some(Stdio::from(o.stdout.unwrap())),
Err(e) => {
previous_command = None;
eprintln!(" Err: {}", e);
},
};
} else {
let stdin = match previous_command {
None => Stdio::inherit(),
Some(o) => o,
};
print!("\r\n");
let child = match Command::new(command)
.args(args)
.stdin(stdin)
.spawn() {
Ok(h) => h,
Err(_) => {
let err_string = format!("Not found : {}", command);
return Err(CmdOutput::from_values(err_string, -1));
},
};
let output = child
.wait_with_output()
.expect("Failed to wait on child");
let status = output.status
.code()
.unwrap_or(255)
.to_string();
previous_command = None;
let format_res = format!("{}",
status);
*&mut rc = format_res.parse::<i16>().unwrap();
*&mut resultat = String::from_utf8(output.stdout).unwrap();
//let _ = &mut resultat.push_str(&format_res);
}
},
};
}
//return resultat;
return Ok(CmdOutput::from_values(resultat, rc));
}
pub fn run_cmd(mycommand: &mut String,
current_number: &mut i32,
conf: &mut SqishConf,
stdout: &mut RawTerminal<Stdout>) {
//NORMAL CMD
if (mycommand != &String::from("")) && (mycommand != &String::from("exit")) {
//Run the command
let comm = replace_signs(&mycommand);
RawTerminal::suspend_raw_mode(&stdout);
let res = handle_input(&comm);
//Display the results
match res {
Ok(_) => (),
Err(e) => {
println!("\r\n{}Got error {}, RC : {:?}{}",
color::Fg(color::Red),
e.outp,
e.rc,
color::Fg(color::Reset));
},
};
//Prepare for a new command
RawTerminal::activate_raw_mode(&stdout);
mycommand.clear();
conf.update_prompt(get_curr_history_number());
write!(stdout, "{}", conf.promptline).unwrap();
stdout.flush();
*current_number += 1;
//EXITTING
} else if mycommand == &String::from("exit") {
write!(stdout, "\r\nThanks for using Sqish!\r\nSayonara \r\n");
RawTerminal::suspend_raw_mode(&stdout);
std::process::exit(0);
//EMPTY LINE
} else {
conf.update_prompt(get_curr_history_number());
write!(stdout, "\r\n{}", conf.promptline).unwrap();
}
stdout.flush();
}
pub fn handle_input(line: &str) -> Result<CmdOutput, CmdOutput> {
//history callback
if (line.len() > 0) && (line.starts_with('!')) {
let command_found = treat_history_callback(line);
match command_found {
Some(c) => {
write_to_history(&c);
return process_line(&c);
},
None => return Err(CmdOutput::from_values(
String::from("No such value"),
255)),
};
//Regular command
} else if line.len() > 0 {
write_to_history(line);
return process_line(line);
//Nothing
} else {
//Command was empty, new line
return Ok(CmdOutput::new_empty());
}
}