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.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# CLI
# CLI
## Запуск под 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
```
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.InputHandler{}
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.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module CLI

go 1.18
20 changes: 20 additions & 0 deletions internal/environment/environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package environment

import "fmt"

type Env map[string]string

func New() Env {
return make(map[string]string)
}

func (env Env) Set(variable, value string) {
env[variable] = value
}

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

import (
"bytes"
"os"
"io"
"fmt"
"strings"
)

type commands map[string]func(*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(b *bytes.Buffer) (*bytes.Buffer, error) {
file, err := os.Open(b.String())
if err != nil {
return nil, err
}
defer file.Close()
b.Reset()
_, err = io.Copy(b, file)
if err != nil {
return nil, err
}
return b, nil
}

func echo(b *bytes.Buffer) (*bytes.Buffer, error) {
content := b.String()
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(_ *bytes.Buffer) (*bytes.Buffer, error) {
os.Exit(0)
return nil, nil
}

func pwd(b *bytes.Buffer) (*bytes.Buffer, error) {
dir, err := os.Getwd()
if err != nil {
return nil, err
}
b.Reset()
b.WriteString(dir)
return b, nil
}
func wc(b *bytes.Buffer) (*bytes.Buffer, error) {
content := b.String()
if len(content) == 0 {
b.WriteString("0 0 0")
return b, nil
}
file, err := os.Open(content)
if err == nil {
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return nil, err
}
content = string(data)
} else {
if content[0] == '"' || content[0] == '\'' {
content = content[1:len(content) - 1]
content = strings.ReplaceAll(content, "\\n", "\n")
}
}

lines := strings.Count(content, "\n")
if len(content) > 0 && !strings.HasSuffix(content, "\n") {
lines++
}
words := len(strings.Fields(content))
characters := len(content)
result := fmt.Sprintf("%d %d %d", lines, words, characters)

b.Reset()
b.WriteString(result)
return b, nil

}
50 changes: 50 additions & 0 deletions internal/executor/executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package executor

import (
"CLI/internal/environment"
"bytes"
"os/exec"
)

// Executor stores a self-implemented functions.
type Executor struct {
cmds commands
env environment.Env
}

// NewExecutor: create a new Executor
func New(env environment.Env) *Executor {
return &Executor{
env: env,
cmds: newCommands(),
}
}


// Execute: gets command and buffer, and returns resulting buffer.
// Parameters:
// - command: string
// - b: buffer with args
// Returns:
// - buffer: resulting buffer.
// - err: error of execute.
func (executor *Executor) Execute(command string, b *bytes.Buffer) (*bytes.Buffer, error) {
if cmd, ok := executor.cmds[command]; ok {
return cmd(b)

} else {
var res *exec.Cmd
if len(b.String()) > 0 {
res = exec.Command(command, b.String())
} else {
res = exec.Command(command)
}
output, err := res.Output()
if err != nil {
return nil, err
}
b.Reset()
b.WriteString(string(output))
return b, nil
}
}
35 changes: 35 additions & 0 deletions internal/handler/inputHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package handler

import (
"CLI/internal/environment"
"CLI/internal/executor"
"CLI/internal/parseline"
"bufio"
"fmt"
"os"
)

// TODO. InputHandler WILL store flags.
type InputHandler struct {
}



// Start: starts Read-Execute-Print Loop
func (handler *InputHandler) Start() {
reader := bufio.NewReader(os.Stdin)
env := environment.New()
exec := executor.New(env)
parser := parseline.New(env)
for {
fmt.Print("\n>>> ")
input, _ := reader.ReadString('\n')
cmd, b := parser.Parse(input)
res, err := exec.Execute(cmd, b)
if err == nil {
fmt.Print(res.String())
} else {
fmt.Print(err)
}
}
}
44 changes: 44 additions & 0 deletions internal/parseline/parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package parseline

import (
"CLI/internal/environment"
"bytes"
"runtime"
"strings"
)

// TODO. Parser WILL store flags for parsing.
type Parser struct {
env environment.Env
}


func New(env environment.Env) *Parser {
return &Parser{
env: env,
}
}

// Parse: parses the received string into a command and buffer
// Parameters:
// - input: string
// Returns:
// - cmd: name of command.
// - buffer: args.
func (p * Parser) Parse(input string) (string, *bytes.Buffer) {
if runtime.GOOS == "windows" {
input = input[: len(input) - 2]
} else {
input = input[: len(input) - 1]
}
words := strings.SplitN(input, " ", 2)

var b *bytes.Buffer

if len(words) > 1 {
b = bytes.NewBuffer([]byte(words[1]))
} else {
b = bytes.NewBuffer(make([]byte, 0))
}
return words[0], b
}
20 changes: 20 additions & 0 deletions scripts/build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@echo off

set APP_NAME=cli-app.exe
set GOOS=windows
set GOARCH=amd64
set OUTPUT_DIR=.\bin

if not exist %OUTPUT_DIR% (
mkdir %OUTPUT_DIR%
)

echo Building: %GOOS%/%GOARCH%...
go build -o %OUTPUT_DIR%\%APP_NAME% -buildvcs=false .\cmd\cli

if exist "%OUTPUT_DIR%\%APP_NAME%" (
echo Path: %OUTPUT_DIR%\%APP_NAME%
) else (
echo Building error.
exit /b 1
)
18 changes: 18 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash

APP_NAME="cli-app"
GOOS="linux"
GOARCH="amd64"
OUTPUT_DIR="./bin"

mkdir -p $OUTPUT_DIR

echo "build for $GOOS/$GOARCH..."
GOOS=$GOOS GOARCH=$GOARCH go build -o $OUTPUT_DIR/$APP_NAME ./cmd/cli

if [ -f "$OUTPUT_DIR/$APP_NAME" ]; then
echo "Build over. Path: $OUTPUT_DIR/$APP_NAME"
else
echo "Building error"
exit 1
fi
11 changes: 11 additions & 0 deletions scripts/run.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@echo off

set BIN_PATH=.\bin\cli-app.exe

if not exist %BIN_PATH% (
echo File not found. Build app (scripts\build.bat).
exit /b 1
)

echo Running
%BIN_PATH%
Loading
Loading