Skip to content
Open
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
Binary file modified bin/cli-app-linux-amd64
100644 → 100755
Binary file not shown.
38 changes: 38 additions & 0 deletions docs/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Ревью проекта

## Архитектура
### Плюсы
- универсальная/гибкая сигнатура функции команды
- хорошее распределение ответственности между классами

### Минусы
- почти нет; изначальная сигнатура команды:
```
func name_command(parseline.Command, *bytes.Buffer) (*bytes.Buffer, error) {}
```
не позволяет реализовывать команды, меняющие состояние Executor

## Реализация
### Плюсы
- легкость добавления новых команд: 1 строка в newCommands() + 1 новая функция со стандартной сигнатурой
- хорошие, подробные тесты для парсера

### Минусы
- плохое разбиение по файлам: все команды в одном файле, все тесты для команд в одном файле, все служебные функции для реализации команд в одном файле
- служебные функции для реализации конкретных команд никак не выделены: нет документации, по названию непонятно, где она нужна, лежат в одном файле с реализацией других команд
- плохая документация тестов: ее почти нет
- плохая документация реализации команд: ее нет вообще
- (pet peeve) комментарии в коде на двух языках

## Репозиторий
### Плюсы
- подробная документация в папке docs: расписаны существующие команды, архитектура проекта, гайд по добавлению новых команд - всё здорово

### Минусы
- Изначально команды, которые работал с файлами, могли работать только в текущей директории и не учитывали переходы между ними.
- нет плашки с тестами в README
- (pet peeve) нет ссылок на документы из docs/папку docs в головном README

## Рекомендации
- можно разбить commands.go на пакеты/файлы
- добавить комментарии к служебным функциям, выделить их в отдельный пакет/файл
6 changes: 3 additions & 3 deletions internal/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (

type Env map[string]string


// Constructor of environment
func New() Env {
env := Env{}
Expand All @@ -18,7 +17,7 @@ func New() Env {
cmd := os.Getenv(v)
env[v] = string(cmd)
}

return env
}

Expand All @@ -33,6 +32,7 @@ func (env Env) Reset() {
env[v] = string(cmd)
}
}

// Set a new variable
func (env Env) Set(variable, value string) {
env[variable] = value
Expand All @@ -44,4 +44,4 @@ func (env Env) Get(variable string) (string, error) {
return v, nil
}
return "", fmt.Errorf("unknown command: %s", variable)
}
}
90 changes: 68 additions & 22 deletions internal/executor/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ package executor
import (
"CLI/internal/parseline"
"bytes"
"errors"
"fmt"
"os"
"strings"
"errors"
"strconv"
"path/filepath"
"regexp"
"strconv"
"strings"

"github.com/spf13/pflag"
)

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


func newCommands() commands {
cmds := make(commands)

// Here you can add new command in CLI
// cmd["name_command"] = name_command
// below you need to implement a command with the following signature:
Expand All @@ -29,7 +29,8 @@ func newCommands() commands {
cmds["pwd"] = pwd
cmds["wc"] = wc
cmds["grep"] = grep

cmds["cd"] = cd
cmds["ls"] = ls

return cmds
}
Expand All @@ -39,16 +40,20 @@ func newCommands() commands {

func cat(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
output := bytes.NewBuffer(nil)
if len(cmd.Args) == 0 {
if len(cmd.Args) == 1 {
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)
for _, filename := range cmd.Args[1:] {
abs_path, _ := filepath.Abs(filepath.Join(cmd.Args[0], filename)) // getting current directory path + filename
data, err := os.ReadFile(abs_path)
if err != nil {
data, err = os.ReadFile(filename)
}
if err != nil {
return nil, fmt.Errorf("cat: %w", err)
}
Expand All @@ -65,10 +70,10 @@ func echo(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {

content := strings.Join(cmd.Args, " ")
if content[0] == '"' {
content = content[1:len(content) - 1]
content = content[1 : len(content)-1]
}
if content[0] == '\'' {
content = content[1:len(content) - 1]
content = content[1 : len(content)-1]
content = strings.ReplaceAll(content, "\\n", "\n")
}
b.Reset()
Expand All @@ -85,24 +90,26 @@ func exit(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
}
}
os.Exit(code)
return nil, nil
return nil, nil
}

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

func wc(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
var input string
if len(cmd.Args) == 0 {
if len(cmd.Args) == 1 {
input = b.String()
} else if len(cmd.Args) > 0 {
data, err := os.ReadFile(cmd.Args[0])
} else if len(cmd.Args) > 1 {
abs_path, _ := filepath.Abs(filepath.Join(cmd.Args[0], cmd.Args[1])) // getting current directory path + filename
data, err := os.ReadFile(abs_path)
if err != nil {
return nil, fmt.Errorf("wc: %w", err)
}
Expand Down Expand Up @@ -141,7 +148,7 @@ func grep(cmd parseline.Command, input *bytes.Buffer) (*bytes.Buffer, error) {
func parseArgs(args []string) (*grepOptions, error) {
opts := &grepOptions{}
fs := pflag.NewFlagSet("grep", pflag.ContinueOnError)

fs.IntVarP(&opts.afterContext, "after-context", "A", 0, "Lines after match")
fs.BoolVarP(&opts.ignoreCase, "ignore-case", "i", false, "Ignore case")
fs.BoolVarP(&opts.wordRegexp, "word-regexp", "w", false, "Whole word match")
Expand All @@ -165,11 +172,11 @@ func parseArgs(args []string) (*grepOptions, error) {

func compileRegex(opts *grepOptions) (*regexp.Regexp, error) {
pattern := opts.pattern

if opts.wordRegexp {
pattern = fmt.Sprintf(`\b%s\b`, pattern)
}

if opts.ignoreCase {
pattern = "(?i)" + pattern
}
Expand Down Expand Up @@ -212,4 +219,43 @@ func processData(data []byte, re *regexp.Regexp, context int) *bytes.Buffer {
}

return &output
}
}

func cd(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
var path string
if len(cmd.Args) == 1 {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("cd: cannot get home directory: %w", err)
}
path = homeDir
} else {
path, _ = filepath.Abs(filepath.Join(cmd.Args[0], cmd.Args[1]))
}

info, err := os.Stat(path)
if err != nil || !info.IsDir() {
return nil, fmt.Errorf("cd: %s: not a directory", path)
}

b.WriteString(path)
return b, nil
}

// Takes first provided argument as path to a directory and lists all files in it.
func ls(cmd parseline.Command, buffer *bytes.Buffer) (*bytes.Buffer, error) {
path := cmd.Args[0]
if len(cmd.Args) == 2 {
path, _ = filepath.Abs(filepath.Join(cmd.Args[0], cmd.Args[1])) // getting correct path
}
entries, err := os.ReadDir(path)
if err != nil {
return nil, fmt.Errorf("ls: %w", err)
}

for _, entry := range entries {
buffer.WriteString(entry.Name() + "\n")
}

return buffer, nil
}
Loading