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
Binary file modified bin/cli-app-linux-amd64
Binary file not shown.
1 change: 1 addition & 0 deletions docs/job_description.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# Parser
Подставляем переменные окружения. Разбиваем строку по пайпам, отделяем команду от аргументов, добавляем в очередь вызовов.


# Executor
Идем по очереди. Получаем buffer с прошлого вызова, и вызываем запуск новой команды. После запуска последней команды, отправляет buffer в stdout и отчитывается перед InputHandler об окончании обработки команды.

Expand Down
124 changes: 73 additions & 51 deletions internal/executor/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type commands map[string]func(parseline.Command, *bytes.Buffer) (*bytes.Buffer,

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 Down Expand Up @@ -119,75 +119,97 @@ func wc(cmd parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
}

func grep(cmd parseline.Command, input *bytes.Buffer) (*bytes.Buffer, error) {
var (
caseInsensitive bool
wordRegexp bool
afterContext int
)

opts, err := parseArgs(cmd.Args)
if err != nil {
return nil, err
}

flagSet := pflag.NewFlagSet("grep", pflag.ContinueOnError)
flagSet.BoolVarP(&caseInsensitive, "ignore-case", "i", false, "Case-insensitive search")
flagSet.BoolVarP(&wordRegexp, "word-regexp", "w", false, "Match whole word")
flagSet.IntVarP(&afterContext, "after-context", "A", 0, "Number of trailing context lines to print")
re, err := compileRegex(opts)
if err != nil {
return nil, fmt.Errorf("regex error: %v", err)
}

if err := flagSet.Parse(cmd.Args); err != nil {
data, err := readData(opts, input)
if err != nil {
return nil, err
}

args := flagSet.Args()
if len(args) < 1 {
return nil, errors.New("pattern is required")
}
pattern := args[0]
result := processData(data, re, opts.afterContext)
return result, nil
}

var reBuilder strings.Builder
if caseInsensitive {
reBuilder.WriteString("(?i)")
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")

if err := fs.Parse(args); err != nil {
return nil, err
}
if wordRegexp {
reBuilder.WriteString(`\b`)

remaining := fs.Args()
if len(remaining) < 1 {
return nil, fmt.Errorf("pattern required")
}
reBuilder.WriteString(pattern)
if wordRegexp {
reBuilder.WriteString(`\b`)

opts.pattern = strings.Trim(remaining[0], `"'`)
if len(remaining) > 1 {
opts.files = remaining[1:]
}

re, err := regexp.Compile(reBuilder.String())
if err != nil {
return nil, fmt.Errorf("invalid regex pattern: %v", err)
return opts, nil
}

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

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

inputData := input.String()
if inputData == "" {
data, err := os.ReadFile(cmd.Args[len(cmd.Args) - 1])
if err != nil {
inputData = ""
}
inputData = string(data)
if opts.ignoreCase {
pattern = "(?i)" + pattern
}
lines := strings.Split(inputData, "\n")
printed := make(map[int]struct{})

for i, line := range lines {
if re.MatchString(line) {
end := i + afterContext
if end >= len(lines) {
end = len(lines) - 1
}
for j := i; j <= end; j++ {
printed[j] = struct{}{}
return regexp.Compile(pattern)
}

func readData(opts *grepOptions, input *bytes.Buffer) ([]byte, error) {
if len(opts.files) > 0 {
var data []byte
for _, f := range opts.files {
fileData, err := os.ReadFile(f)
if err != nil {
return nil, fmt.Errorf("error reading %s: %v", f, err)
}
data = append(data, fileData...)
}
return data, nil
}
return input.Bytes(), nil
}

func processData(data []byte, re *regexp.Regexp, context int) *bytes.Buffer {
lines := bytes.Split(data, []byte{'\n'})
var output bytes.Buffer
remaining := 0

for _, line := range lines {
if remaining > 0 {
output.Write(line)
output.WriteByte('\n')
remaining--
}

var resultBuffer bytes.Buffer
for i := 0; i < len(lines); i++ {
if _, ok := printed[i]; ok {
resultBuffer.WriteString(lines[i])
resultBuffer.WriteByte('\n')
if re.Match(line) {
output.Write(line)
output.WriteByte('\n')
remaining = context
}
}

return &resultBuffer, nil
return &output
}
1 change: 1 addition & 0 deletions internal/executor/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ func TestGrepOverlappingContext(t *testing.T) {
expected := `Lorem ipsum dolor sit amet,
consectetur adipiscing elit.
Lorem IPSUM dolor sit amet,
Lorem IPSUM dolor sit amet,
Praesent non Word_WORD word.
Word at start.
`
Expand Down
48 changes: 29 additions & 19 deletions internal/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,34 @@ func New(env environment.Env) *Executor {
}
}

// Execute: execute commands, and returns resulting buffer.
// Parameters:
// - commands: []parseline.Command
// Returns:
// - buffer: resulting buffer.
// - err: error of execute.
func (executor *Executor) Execute(commands []parseline.Command) (*bytes.Buffer, error) {
var err error
buffer := bytes.NewBufferString("")
for _, cmd := range commands {
buffer, err = executor.execute(cmd, buffer)
if err != nil {
return nil, err
}
}
return buffer, nil
}


type grepOptions struct {
ignoreCase bool
wordRegexp bool
afterContext int
pattern string
files []string
}


func (executor *Executor) execute(command parseline.Command, b *bytes.Buffer) (*bytes.Buffer, error) {
if cmd, ok := executor.cmds[command.Name]; ok {
return cmd(command, b)
Expand Down Expand Up @@ -53,22 +81,4 @@ func (executor *Executor) execute(command parseline.Command, b *bytes.Buffer) (*
}
return bytes.NewBufferString(string(output)), nil
}
}

// Execute: execute commands, and returns resulting buffer.
// Parameters:
// - commands: []parseline.Command
// Returns:
// - buffer: resulting buffer.
// - err: error of execute.
func (executor *Executor) Execute(commands []parseline.Command) (*bytes.Buffer, error) {
var err error
buffer := bytes.NewBufferString("")
for _, cmd := range commands {
buffer, err = executor.execute(cmd, buffer)
if err != nil {
return nil, err
}
}
return buffer, nil
}
}