Compare commits

8 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
5 changed files with 328 additions and 23 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.
@ -22,6 +23,9 @@ TODO (Reformat):
TODO (features): TODO (features):
* Add a && * Add a &&
TODO (Bugz):
* Autocomplete shits itself when typing a path starting with /
## 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

@ -408,23 +408,23 @@ 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() {
@ -614,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));

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());
}
}