Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/workflows/go.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Go

on:
push

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.20'

- name: Build
run: go build -v ./...

- name: Test
run: go test -v ./...
Empty file added .gitignore
Empty file.
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
# CLI
# CLI

[![CI Tests](https://github.com/HSE-Software-Development/CLI/actions/workflows/go.yaml/badge.svg)](https://github.com/HSE-Software-Development/CLI/actions)

Простой интерпретатор командной строки, поддерживающий [самореализованные команды](#поддерживаемые-команды), вызов внешних программ, а также поддержку переменных, своих и окружения.

## Поддерживаемые команды
- cat [FILE] — вывести на экран содержимое файла
- echo — вывести на экран свой аргумент (или аргументы)
- wc [FILE] — вывести количество строк, слов и байт в файле
- pwd — распечатать текущую директорию
- exit — выйти из интерпретатора

## Запуск под Linux
``` bash
# build
chmod +x scripts/build.sh
./scripts/build.sh

# Run
chmod +x scripts/run.sh
./scripts/run.sh
```

## Запуск под Windows
``` bash
# build
scripts\build.bat

#Run
#scripts\run.bat
.\bin\cli-app.exe
```

## Переменные окружения

При запуске, программа подгружает переменные окружения с вашего устройства.
При запуске под unix-подобной системой это будут:
- "PWD", "SHELL", "TERM", "USER", "OLDPWD", "LS_COLORS", "MAIL", "PATH", "LANG", "HOME", "_*"
Binary file added bin/cli-app
Binary file not shown.
Binary file added bin/cli-app.exe
Binary file not shown.
22 changes: 22 additions & 0 deletions cmd/cli/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"CLI/internal/handler"
"fmt"
"os"
"os/signal"
"syscall"
)

func main() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

go func() {
sig := <-sigChan
fmt.Println("\nexit:", sig)
os.Exit(0)
}()
handler := handler.New()
handler.Start()
}
Binary file added docs/UML.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/usecase.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module CLI

go 1.18

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.10.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
35 changes: 35 additions & 0 deletions internal/environment/environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package environment

import (
"fmt"
"os"
)

type Env map[string]string


// Constructor of environment
func New() Env {
env := Env{}
env_var := []string{
"PWD", "SHELL", "TERM", "USER", "OLDPWD", "LS_COLORS", "MAIL", "PATH", "LANG", "HOME", "_*",
}
for _, v := range env_var {
cmd := os.Getenv(v)
env[v] = string(cmd)
}

return env
}
// Set a new variable
func (env Env) Set(variable, value string) {
env[variable] = value
}

// Get a new variable
func (env Env) Get(variable string) (string, error) {
if v, ok := env[variable]; ok {
return v, nil
}
return "", fmt.Errorf("unknown command: %s", variable)
}
36 changes: 36 additions & 0 deletions internal/environment/environment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package environment

import (
"testing"
"fmt"

"github.com/stretchr/testify/assert"
)

func TestConstructor(t *testing.T) {
env := New()
assert.Equal(t, len(env), 11)
fmt.Println("------")
fmt.Println(env["PWD"])
fmt.Println("------")
}


func TestEnv(t *testing.T) {
env := New()
variables := map[string]string {
"111": "xxx",
"222": "yyy",
"333": "zzz",
}
for k, v := range variables {
env.Set(k, v)
}
for k, v := range variables {
if val, err := env.Get(k); err != nil {
assert.Error(t, err)
} else {
assert.Equal(t, v, val)
}
}
}
105 changes: 105 additions & 0 deletions internal/executor/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package executor

import (
"CLI/internal/parseline"
"bytes"
"fmt"
"os"
"strings"
"errors"
"strconv"
)

type commands map[string]func(parseline.Command, *bytes.Buffer) (*bytes.Buffer, error)


func newCommands() commands {
cmds := make(commands)
cmds["cat"] = cat
cmds["echo"] = echo
cmds["exit"] = exit
cmds["pwd"] = pwd
cmds["wc"] = wc
return cmds
}

func cat(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
output := bytes.NewBuffer(nil)
if len(cmd.Args) == 0 {
if b != nil {
_, err := output.Write(b.Bytes())
return output, err
}
return nil, errors.New("no input provided")
}

for _, filename := range cmd.Args {
data, err := os.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("cat: %w", err)
}
output.Write(data)
}

return output, nil
}

func echo(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
if len(cmd.Args) == 0 {
return bytes.NewBufferString(""), nil
}

content := strings.Join(cmd.Args, " ")
if content[0] == '"' {
content = content[1:len(content) - 1]
}
if content[0] == '\'' {
content = content[1:len(content) - 1]
content = strings.ReplaceAll(content, "\\n", "\n")
}
b.Reset()
b.WriteString(content)
return b, nil
}

func exit(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
code := 0
if len(cmd.Args) > 0 {
if c, err := strconv.Atoi(cmd.Args[0]); err == nil {
code = c
}
}
os.Exit(code)
return nil, nil
}

func pwd(cmd parseline.Command, _ *bytes.Buffer) (*bytes.Buffer, error) {
dir, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("pwd: %w", err)
}
output := bytes.NewBufferString(dir)
return output, nil
}

func wc(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
var input []byte
if b != nil {
input = b.Bytes()
} else if len(cmd.Args) > 0 {
data, err := os.ReadFile(cmd.Args[0])
if err != nil {
return nil, fmt.Errorf("wc: %w", err)
}
input = data
} else {
return nil, errors.New("wc: no input provided")
}

lines := bytes.Count(input, []byte{'\n'})
words := len(bytes.Fields(input))
chars := len(input)

result := fmt.Sprintf("%d %d %d", lines, words, chars)
return bytes.NewBufferString(result), nil
}
Loading