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.
94 changes: 93 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,93 @@
# 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)

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

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

## Поддерживаемые команды
Данный интерпретатор уже поддерживает многие команды, такие как cat, echo, wc, grep и многие другие.
Подробнее о них можете узнать в [Commands.md](docs/Commands.md)

## Запуск под macOS/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", "_*"

## Тестирование

Для запуска всех unit-test программы, запустите:
```bash
go test -v ./...
```

## Выбор библиотеки для работы с флагами

### Почему `pflag`?

Основные причины выбора `pflag` для вашего CLI-проекта:

1. **Совместимость с POSIX/GNU стилем флагов**
- Поддержка коротких (`-v`) и длинных (`--verbose`) флагов
- Возможность комбинирования коротких флагов (`-a -b -c` → `-abc`)

2. **Интеграция с Go-экосистемой**
- Разработана создателями Cobra (популярного фреймворка для CLI)
- Широко используется в известных Go-проектах (Kubernetes, Docker CLI)

3. **Богатый функционал**
```go
flagSet.IntVarP(&port, "port", "p", 8080, "server port")
```
- Поддержка типизированных флагов (int, bool, string и др.)
- Автоматическая генерация help-сообщений


### Сравнительная таблица

| Библиотека | POSIX флаги | Подкоманды | Типизация | Сложность | Размер |
|------------|------------|------------|-----------|-----------|--------|
| `flag` | ❌ | ❌ | ✅ | Low | 0MB |
| `pflag` | ✅ | ❌ | ✅ | Medium | 0.5MB |
| `cli` | ✅ | ✅ | ✅ | High | 2MB |
| `cobra` | ✅ | ✅ | ✅ | High | 3MB |
| `kong` | ✅ | ✅ | ✅ | Very High | 1MB |

### Итоговый выбор

`pflag` идеально подходит для вашего проекта потому что:
1. Обеспечивает нужную функциональность для `grep` (поддержка `-i`, `-w`, `-A`)
2. Не вводит избыточных зависимостей
3. Сохраняет совместимость с Unix-традициями
4. Позволяет легко расширять функционал в будущем


## Contribution

Если вам так понравился наш продукт, что у вас появилось желание его доработать, вы всегда можете добавить в него свой функционал.

[Как мне это сделать?](docs/Contribution.md)

Binary file added bin/cli-app
Binary file not shown.
Binary file added bin/cli-app-darwin-arm64
Binary file not shown.
Binary file added bin/cli-app-linux-amd64
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()
}
142 changes: 142 additions & 0 deletions docs/Commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
## CLI Commands Documentation

### `cat`
**Description**: Concatenate and print file contents or stdin
**Usage**: `cat [FILE]...`
**Arguments**:
- `FILE`: One or more files to display (optional, reads from stdin if omitted)

**Behavior**:
- With files: Outputs contents of all specified files concatenated
- Without files: Outputs contents from stdin (if provided)
- Returns error if no input source is available

**Examples**:
```bash
> cat file.txt
> echo "text" | cat
```

---

### `echo`
**Description**: Print arguments to stdout
**Usage**: `echo [STRING]...`
**Arguments**:
- `STRING`: Text to output (supports quoted strings and `\n` escapes)

**Features**:
- Removes outer quotes (`"` or `'`) if present
- Converts `\n` to newlines in single-quoted strings
- Joins multiple arguments with spaces

**Examples**:
```bash
> echo hello world
> echo '"quoted"' → quoted
> echo -e 'line1\nline2' → line1[newline]line2
```

---

### `exit`
**Description**: Terminate the shell
**Usage**: `exit [STATUS]`
**Arguments**:
- `STATUS`: Exit code (default: 0)

**Behavior**:
- Accepts numeric exit code (0-255)
- Immediately terminates process with specified code

**Examples**:
```bash
> exit
> exit 1
```

---

### `pwd`
**Description**: Print working directory
**Usage**: `pwd`
**Output**: Absolute path of current directory

**Error Cases**:
- Fails if directory cannot be determined

**Example**:
```bash
> pwd
/home/user/project
```

---

### `wc`
**Description**: Count lines, words, and characters
**Usage**: `wc [FILE]`
**Arguments**:
- `FILE`: File to analyze (reads from stdin if omitted)

**Output Format**: `lines words bytes`

**Behavior**:
- Counts:
- Lines (`\n` separated)
- Words (whitespace-separated)
- Bytes (raw length)
- Requires either file or stdin input

**Examples**:
```bash
> wc file.txt
3 15 102
> echo "hello world" | wc
1 2 12
```

---

### `grep`
**Description**: Pattern search in text
**Usage**: `grep [OPTIONS] PATTERN`
**Arguments**:
- `PATTERN`: Regular expression to search

**Options**:
| Flag | Description |
|------|--------------------------------------|
| `-i` | Case-insensitive search |
| `-w` | Match whole words only |
| `-A N` | Print N lines after each match |

**Behavior**:
- Processes stdin only
- Supports PCRE regex syntax
- Handles overlapping `-A` contexts
- Returns error for invalid regex

**Examples**:
```bash
> grep "error" log.txt
> grep -i -A 1 "warning" < input.txt
> echo "test" | grep -w "test"
```

---

### Notes
1. All commands:
- Accept input via stdin when piped
- Return `*bytes.Buffer` with output
- Propagate errors with context

2. Common patterns:
- Empty args → use stdin (where applicable)
- File operations → relative to current `pwd`
- String parsing → handles basic quoting

3. Error handling:
- Commands return descriptive errors
- Exit codes follow Unix conventions (where applicable)
97 changes: 97 additions & 0 deletions docs/Contribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Contribution

Для того, чтобы добавить обработку новых команд в CLI, вам необходимо:
- Создать fork этого проекта
- В файле [commands.go](../internal/executor/commands.go) добавить функцию со следующей сигнатурой, [требования/советы](#требованиясоветы-к-реализации):
```go
func name_cmd(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {}
```
- Напиши тесты для новой функции в фаил [commands_test.go](../internal/executor/commands_test.go)
```go
//Напишите название новой функции
func TestNameCommand(t *testing.T) {
tests := []struct {
name string // название теста
cmd parseline.Command // команда, которую хотите выполнить
input *bytes.Buffer // буффер от прошлой функции в pipeline
want string // ожидаемый результат
wantErr bool // должена ли функция возвращать ошибку
}{
// Задайте параметры тестов
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Вместо NAME_CMD напишите название новой функции
got, err := NAME_CMD(tt.cmd, tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("wc() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != nil && got.String() != tt.want {
t.Errorf("wc() = %v, want %v", got.String(), tt.want)
}
})
}
}
```

- После прохождения тестов, добавьте новый функционал в систему. Для этого вернемся в фаил [commands.go](../internal/executor/commands.go) и в методе New() добавьте инициализацию вашей команды.
```go
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:
// func(parseline.Command, *bytes.Buffer) (*bytes.Buffer, error)
cmds["cat"] = cat
cmds["echo"] = echo
cmds["exit"] = exit
cmds["pwd"] = pwd
cmds["wc"] = wc
cmds["grep"] = grep


return cmds
}
```
- Дополните документацию по новой команде в [Commands.md](Commands.md)

## Требования/советы к реализации:
- На вход подается структура parseline.Command, которая задана в файле [parser.go](../internal/parseline/parser.go)
```go
// Command store name of command and it's flags and args
type Command struct {
Name string
Args []string
}
```

- Парсер разделяет получаемую команду на command и args, в буффере будет записана только возвращаемое значение предыдущей команды в pipeline. Буффер всегда будет инициализован.

- В случае если функция завершилась без ошибки, но ничего не возвращает, возвращать не nil буффер!!!
```go
return bytes.NewBufferString(""), nil // так


return nil, nil // не так
```
- В случае если во время испольнения произошла какая-то ошибка, вы вольны создавать ее как через пакет errors, так и через пакет fmt
```go
return nil, errors.New("no input provided") // так, в случае создания ошибки

return nil, fmt.Errorf("cat: %w", err) // так, в случае обертки надо полученной ошибкой
```
- Parser соберет command без удаление скобок и ковычек, примеры:
```
>>> echo "111"
```
На испольнение будет передана команда:
```go
Command {
name: "echo",
args: [`"111"`],
}
```


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.
11 changes: 11 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
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/spf13/pflag v1.0.6 // indirect
github.com/stretchr/testify v1.10.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading