This commit is contained in:
Tobias P.L Wennberg 2025-01-10 13:29:56 +01:00
commit 15eeadf5d3
20 changed files with 592 additions and 0 deletions

22
command/cat/cat.go Normal file
View File

@ -0,0 +1,22 @@
package cat
import (
"bbash/environment"
"bbash/input_parser"
"fmt"
"os"
"path/filepath"
)
func Cat(in input_parser.Input, env *environment.Env) {
file := filepath.Join(env.Path, in.Args)
bytea_str, err := os.ReadFile(file)
if err != nil {
fmt.Print(fmt.Sprintf("Error opening file: %s", err.Error()))
return
}
fmt.Print(string(bytea_str))
}

34
command/cd/cd.go Normal file
View File

@ -0,0 +1,34 @@
package cd
import (
"bbash/environment"
"bbash/input_parser"
"fmt"
"os"
"path/filepath"
)
func Cd(in input_parser.Input, env *environment.Env) {
var new_path string
if in.Args == "" {
new_path = env.Env["HOME"]
} else {
if in.Args[0] == '/' {
new_path = in.Args
} else {
new_path = filepath.Join(env.Path, in.Args)
}
}
file, err := os.Stat(new_path)
if err != nil {
fmt.Print(fmt.Sprintf("Error changing directory %s", err.Error()))
return
}
if !file.IsDir() {
fmt.Print("New path is not a directory")
return
}
env.Path = new_path
}

89
command/command.go Normal file
View File

@ -0,0 +1,89 @@
package command
import (
"bbash/command/cat"
"bbash/command/cd"
"bbash/command/cp"
"bbash/command/echo"
"bbash/command/head"
"bbash/command/help"
"bbash/command/ls"
"bbash/command/man"
"bbash/command/mv"
"bbash/command/pwd"
"bbash/command/rm"
"bbash/command/touch"
"bbash/environment"
"bbash/input_parser"
"fmt"
"os"
"strings"
)
var path_command []string
func Init(env *environment.Env) {
init_path(env)
}
func Run_command(in input_parser.Input, env *environment.Env) {
switch in.Instruction {
case "pwd": pwd.Pwd(in, env)
case "echo": echo.Echo(in, env)
case "ls": ls.Ls(in, env)
case "cd": cd.Cd(in, env)
case "man": man.Man(in, env)
case "cat": cat.Cat(in, env)
case "head": head.Head(in, env)
case "touch": touch.Touch(in, env)
case "rm": rm.Rm(in, env)
case "cp": cp.Cp(in, env)
case "mv": mv.Mv(in,env)
case "help": help.Help(in,env)
default:
if !run_by_path(in, env) {
fmt.Println(fmt.Sprintf("No such command! (%s)", in.Instruction))
}
}
}
// Returns if anything was run or not
func run_by_path(in input_parser.Input, env *environment.Env) bool {
fmt.Print(env.Env["PATH"])
return false
}
func init_path(env *environment.Env) {
path := strings.Split(env.Env["PATH"], ":")
for _, a_path := range path {
recursive_executable_finder(a_path)
}
}
func recursive_executable_finder(path string) {
dir, err := os.ReadDir(path)
if err != nil {
return
}
for _, f := range dir {
if f.IsDir() {
recursive_executable_finder(f.Name())
continue
}
if !is_executable(f.Name()) {
println("not exec")
continue
}
println(f.Name())
path_command = append(path_command, f.Name())
}
}
func is_executable( name string) bool {
return true
stat, _ := os.Stat(name)
mode := stat.Mode()
return mode&(1<<0) != 0 || // Others' execute bit
mode&(1<<3) != 0 || // Group's execute bit
mode.Perm()&(1<<6) != 0 // Owner's execute bit
}

27
command/command_test.go Normal file
View File

@ -0,0 +1,27 @@
package command
import(
"io/fs"
"testing"
)
func Test_is_executable(t *testing.T) {
tests := []struct {
name string // description of this test case
// Named input parameters for target function.
mode fs.FileMode
want bool
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := is_executable(tt.mode)
// TODO: update the condition below to compare got with tt.want.
if true {
t.Errorf("is_executable() = %v, want %v", got, tt.want)
}
})
}
}

36
command/cp/cp.go Normal file
View File

@ -0,0 +1,36 @@
package cp
import (
"bbash/environment"
"bbash/input_parser"
"fmt"
"os"
"path/filepath"
"strings"
)
func Cp(in input_parser.Input, env *environment.Env) {
args := strings.SplitN(in.Args, " ", 2)
if len(args) != 2 {
fmt.Print("No right amount of args")
return
}
file_path_source := filepath.Join(env.Path, args[0])
file_path_dest := filepath.Join(env.Path, args[1])
source, err := os.ReadFile(file_path_source)
if err != nil {
fmt.Println(fmt.Sprintf("Error opening file: %s", err.Error()))
return
}
dest, err := os.Create(file_path_dest)
defer dest.Close()
if err != nil {
fmt.Println(fmt.Sprintf("Error opening file: %s", err.Error()))
return
}
dest.Write(source)
}

12
command/echo/echo.go Normal file
View File

@ -0,0 +1,12 @@
package echo
import (
"bbash/input_parser"
"fmt"
"bbash/environment"
)
func Echo(in input_parser.Input, _ *environment.Env) {
fmt.Print(in.Args)
}

50
command/head/head.go Normal file
View File

@ -0,0 +1,50 @@
package head
import (
"bbash/environment"
"bbash/input_parser"
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func Head(in input_parser.Input, env *environment.Env) {
args := strings.SplitN(in.Args, " ", 2)
var file_path string
var lines int
if len(args) >= 1 {
file_path = strings.TrimSpace(args[0])
lines = 10
} else {
fmt.Print(fmt.Sprintf("No arguments provided"))
return
}
if len(args) == 2 {
lines1, err := strconv.Atoi(strings.TrimSpace(args[1]))
lines = lines1
if err != nil {
fmt.Print(fmt.Sprintf("Second argument must be a integer"))
return
}
}
file, err := os.Open(file_path)
if err != nil {
fmt.Println(fmt.Sprintf("Error opening file: %s", err.Error()))
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for i := 0; i < lines-1; i++ {
if(!scanner.Scan()) {
return
}
fmt.Println(scanner.Text())
}
scanner.Scan()
fmt.Print(scanner.Text())
}

28
command/help/help.go Normal file
View File

@ -0,0 +1,28 @@
package help
import (
"bbash/input_parser"
"fmt"
"bbash/environment"
)
func Help(in input_parser.Input, _ *environment.Env) {
fmt.Print(`
Welcome to the bornagain bornagain shell!
The options are:
pwd: Output current working directory
echo: Echoes the input argument
ls: List Sources in the current working directory
cd: Change to directory given in the argument
man: Manual for the terminal options
cat: Outputs the contents of the given file
head: Outputs first lines of a given file. 10 if non provided. Argument <file> <number of lines>
touch: Creates the given file
rm: Removes the given file
cp: Copies the given file. Argument: <source> <destination>
mv: Moves a given file. Argument: <source> <destination>
help: Prints this message
`)
}

21
command/ls/ls.go Normal file
View File

@ -0,0 +1,21 @@
package ls
import (
"bbash/environment"
"bbash/input_parser"
"fmt"
"os"
)
func Ls(in input_parser.Input, env *environment.Env) {
fs, err := os.ReadDir(env.Path)
if err != nil {
fmt.Print(fmt.Sprintf("Error opening directory %s", env.Path))
}
for _, f := range fs {
fmt.Print(f.Name())
fmt.Println()
}
}

37
command/man/man.go Normal file
View File

@ -0,0 +1,37 @@
package man
import (
"bbash/environment"
"bbash/input_parser"
"fmt"
)
func Man(in input_parser.Input, env *environment.Env) {
switch in.Instruction {
case "pwd":
fmt.Print("Output current working directory")
case "echo":
fmt.Print("Echoes the input argument")
case "ls":
fmt.Print("List Sources in the current working directory")
case "cd":
fmt.Print("Change to directory given in the argument")
case "man":
fmt.Print("Manual for the terminal options")
case "cat":
fmt.Print("Outputs the contents of the given file")
case "head":
fmt.Print("Outputs first lines of a given file. 10 if non provided. Argument <file> <number of lines>")
case "touch":
fmt.Print("Creates the given file")
case "rm":
fmt.Print("Removes the given file")
case "cp":
fmt.Print("Copies the given file. Argument: <source> <destination>")
case "mv":
fmt.Print("Moves a given file. Argument: <source> <destination>")
default: fmt.Println(fmt.Sprintf("No such command! (%s)", in.Instruction))
}
}

47
command/mv/mv.go Normal file
View File

@ -0,0 +1,47 @@
package mv
import (
"bbash/environment"
"bbash/input_parser"
"fmt"
"os"
"path/filepath"
"strings"
)
func Mv(in input_parser.Input, env *environment.Env) {
args := strings.SplitN(in.Args, " ", 2)
if len(args) != 2 {
fmt.Print("No right amount of args")
return
}
file_path_source := filepath.Join(env.Path, args[0])
file_path_dest := filepath.Join(env.Path, args[1])
source, err := os.ReadFile(file_path_source)
if err != nil {
fmt.Println(fmt.Sprintf("Error opening file: %s", err.Error()))
return
}
dest, err := os.Create(file_path_dest)
defer dest.Close()
if err != nil {
fmt.Println(fmt.Sprintf("Error opening file: %s", err.Error()))
return
}
_, err = dest.Write(source)
if err != nil {
fmt.Println(fmt.Sprintf("Error writing destination: %s", err.Error()))
return
}
err = os.Remove(file_path_source)
if err != nil {
fmt.Println(fmt.Sprintf("Error removing source: %s", err.Error()))
return
}
}

12
command/pwd/pwd.go Normal file
View File

@ -0,0 +1,12 @@
package pwd
import (
"bbash/input_parser"
"fmt"
"bbash/environment"
)
func Pwd(in input_parser.Input, env *environment.Env) {
fmt.Print(env.Path)
}

31
command/rm/rm.go Normal file
View File

@ -0,0 +1,31 @@
package rm
import (
"bbash/environment"
"bbash/input_parser"
"fmt"
"os"
"path/filepath"
)
func Rm(in input_parser.Input, env *environment.Env) {
file_path := filepath.Join(env.Path, in.Args)
stat, err := os.Stat(file_path)
if os.IsNotExist(err) {
fmt.Print(fmt.Sprintf("File does not exist"))
return
}
if stat.IsDir() {
fmt.Print(fmt.Sprintf("%s Is a folder, not a file!", file_path))
return
}
err = os.Remove(file_path)
if err != nil {
fmt.Print(fmt.Sprintf("Error removing file or folder: %s", err.Error()))
return
}
}

27
command/touch/touch.go Normal file
View File

@ -0,0 +1,27 @@
package touch
import (
"bbash/environment"
"bbash/input_parser"
"fmt"
"os"
"path/filepath"
)
func Touch(in input_parser.Input, env *environment.Env) {
file_path := filepath.Join(env.Path, in.Args)
_, err := os.Stat(file_path)
if !os.IsNotExist(err) {
fmt.Print(fmt.Sprintf("File alredy exist!"))
return
}
file, err := os.Create(file_path)
if err != nil {
fmt.Print(fmt.Sprintf("Error creating file: %s", err.Error()))
return
}
defer file.Close()
}

13
environment/env_test.go Normal file
View File

@ -0,0 +1,13 @@
package environment
import (
"testing"
"fmt"
)
func TestEnv(t *testing.T) {
env := Get_env()
fmt.Print("PATH: ")
fmt.Println(env.Path)
}

View File

@ -0,0 +1,30 @@
package environment
import (
"log"
"os"
"strings"
)
type Env struct {
Path string
Env map[string]string
}
func Get_env() Env {
pwd, err := os.Getwd()
if err != nil {
log.Fatal(err.Error())
}
env := make(map[string]string)
for _, s_env := range os.Environ() {
split := strings.Split(s_env, "=")
env[split[0]] = split[1]
}
return Env{
Path: pwd,
Env: env,
}
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module bbash
go 1.23.4

View File

@ -0,0 +1,36 @@
package input_parser
import (
"bufio"
"log"
"os"
"strings"
)
type Input struct {
// The instruction a.k.a first argument
Instruction string
// The args, currently just the string after the instruction
Args string
}
func Parse() Input {
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err.Error())
}
split := strings.SplitN(string(input), " ", 2)
instruction := strings.TrimSpace(split[0])
var arg string
if len(split) == 2 {
arg = strings.TrimSpace(split[1])
} else {
arg = ""
}
return Input {
Instruction: instruction,
Args: arg,
}
}

16
input_parser/test_test.go Normal file
View File

@ -0,0 +1,16 @@
package input_parser
import (
"testing"
"fmt"
)
func TestParse(t *testing.T) {
Parse("")
Parse("hej")
got := Parse("echo \" jej asdsa dsa \"")
fmt.Println(got.Instruction)
fmt.Println(got.Args)
}

21
main.go Normal file
View File

@ -0,0 +1,21 @@
package main
import (
"bbash/command"
"bbash/environment"
"bbash/input_parser"
"fmt"
)
func main() {
env := environment.Get_env()
command.Init(&env)
for {
fmt.Print(fmt.Sprintf("%s > ", env.Path))
in_parsed := input_parser.Parse()
command.Run_command(in_parsed, &env)
fmt.Println()
}
}