Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
eckertalex committed Nov 12, 2024
0 parents commit d963919
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 0 deletions.
62 changes: 62 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
VERSION := $(shell git describe --abbrev=0 --tags --always)
LDFLAGS := -X main.Version=$(VERSION)

# ==================================================================================== #
# HELPERS
# ==================================================================================== #

## help: print this help message
.PHONY: help
help:
@echo "Usage:"
@sed -n "s/^##//p" ${MAKEFILE_LIST} | column -t -s ":" | sed -e "s/^/ /"

.PHONY: confirm
confirm:
@echo "Are you sure? (y/n) \c"
@read answer; \
if [ "$$answer" != "y" ]; then \
echo "Aborting."; \
exit 1; \
fi

# ==================================================================================== #
# QUALITY CONTROL
# ==================================================================================== #

## audit: run quality control checks
.PHONY: audit
audit: test
go mod tidy -diff
go mod verify
test -z "$(shell gofmt -l .)"
go vet ./...
go run honnef.co/go/tools/cmd/staticcheck@latest -checks=all,-ST1000,-U1000 ./...
go run golang.org/x/vuln/cmd/govulncheck@latest ./...

## test: run all tests
.PHONY: test
test:
go test -v -race -buildvcs ./...

# ==================================================================================== #
# DEVELOPMENT
# ==================================================================================== #

## tidy: tidy and format all .go files
.PHONY: tidy
tidy:
go mod tidy
go fmt ./...

## build/edgo: build the cmd/edgo application
.PHONY: build/edgo
build/edgo:
@go build -v -ldflags "$(LDFLAGS)" -o=./tmp/edgo ./cmd/edgo

## run/edgo: run the cmd/edgo application
.PHONY: run/edgo
run/edgo: build/edgo
@./tmp/edgo

# vim: set tabstop=4 shiftwidth=4 noexpandtab
50 changes: 50 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# edgo

A minimal implementation of the classic Unix line editor `ed` written in Go.

## Usage

### Basic Operation

```bash
# Start with empty buffer
edgo

# Edit a file
edgo filename.txt

# Start with custom prompt
edgo -p "* "
```

### Command Line Flags

- `-p string`: Set custom prompt (default "\*")
- `-v`: Display version information and exit

### Example Session

```
$ edgo
*a
hello world
line two
.
*w file.txt
2
*q
```

Please make sure to update tests as appropriate.

## TODO

- [ ] print buffer size on load
- [ ] don't show prompt when appending/inserting
- [ ] support basic error messages
- [ ] enable showing error messages with H similar to P
- [ ] Line ranges (e.g., `1,5p`)
- [ ] Search commands (`/pattern/`)
- [ ] Substitute command (`s/old/new/`)
- [ ] Copy/move lines
- [ ] Undo functionality
11 changes: 11 additions & 0 deletions cmd/edgo/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import "strings"

type Command struct {
name string
}

func parseCommand(input string) Command {
return Command{name: strings.TrimLeft(input, " ")}
}
30 changes: 30 additions & 0 deletions cmd/edgo/commands.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import "fmt"

func (ed *Editor) cmdQuit() error {
ed.running = false
return nil
}

func (ed *Editor) cmdTogglePrompt() error {
ed.isPromptShown = !ed.isPromptShown
return nil
}

func (ed *Editor) cmdToggleShowFullError() error {
ed.isFullErrorShown = !ed.isFullErrorShown

if ed.isFullErrorShown {
ed.cmdShowLastError()
}

return nil
}

func (ed *Editor) cmdShowLastError() error {
if ed.lastError != nil {
fmt.Fprintln(ed.writer, ed.lastError)
}
return nil
}
86 changes: 86 additions & 0 deletions cmd/edgo/editor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"bufio"
"fmt"
"io"
)

type Editor struct {
lastError error
reader io.Reader
writer io.Writer
prompt string
mode EditorMode
isPromptShown bool
isFullErrorShown bool
running bool
}

func NewEditor(reader io.Reader, writer io.Writer, prompt string, isPromptShown bool) *Editor {
return &Editor{
reader: reader,
writer: writer,
prompt: prompt,
isPromptShown: isPromptShown,
mode: ModeCommand,
}
}

func (ed *Editor) printPrompt() {
if ed.isPromptShown {
fmt.Fprint(ed.writer, ed.prompt)
}
}

func (ed *Editor) printError() {
fmt.Fprintln(ed.writer, "?")
if ed.isFullErrorShown {
fmt.Fprintln(ed.writer, ed.lastError)
}
}

func (ed *Editor) executeCommand(cmd Command) {
var err error

switch cmd.name {
case "P":
err = ed.cmdTogglePrompt()
case "h":
err = ed.cmdShowLastError()
case "H":
err = ed.cmdToggleShowFullError()
case "q":
err = ed.cmdQuit()
case "Q":
err = ed.cmdQuit()
default:
err = ErrUnknownCommand
ed.printError()
}

if err != nil {
ed.lastError = err
}
}

func (ed *Editor) Run() error {
scanner := bufio.NewScanner(ed.reader)
ed.running = true

for ed.running {
ed.printPrompt()

if !scanner.Scan() {
if err := scanner.Err(); err != nil {
return fmt.Errorf("input error: %w", err)
}
break
}

cmd := parseCommand(scanner.Text())
ed.executeCommand(cmd)
}

return nil
}
5 changes: 5 additions & 0 deletions cmd/edgo/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package main

import "errors"

var ErrUnknownCommand = errors.New("unknown command")
35 changes: 35 additions & 0 deletions cmd/edgo/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"flag"
"fmt"
"os"
)

var Version = "v0.0.0"

func main() {
var (
promptFlag = flag.String("p", "", "Specify a command prompt. This may be toggled on and off with the P command.")
displayVersion = flag.Bool("v", false, "Display version and exit")
)

flag.Parse()

if *displayVersion {
fmt.Printf("%s version %s\n", os.Args[0], Version)
os.Exit(0)
}

isPromptShown := *promptFlag != ""
prompt := *promptFlag
if prompt == "" {
prompt = "*"
}

editor := NewEditor(os.Stdin, os.Stdout, prompt, isPromptShown)
if err := editor.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
7 changes: 7 additions & 0 deletions cmd/edgo/mode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

type EditorMode int

const (
ModeCommand EditorMode = iota
)
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/eckertalex/edgo

go 1.23.2
8 changes: 8 additions & 0 deletions test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
A spark begins its climb in line one,
By line two, the night’s song has begun.
A hush drifts down like snow by three,
Then line four hums a midnight key.
Five's a whisper, soft and slow,
Six draws shadows long and low.
Seven spins the stars on cue,
Eight wraps the world in twilight's blue.

0 comments on commit d963919

Please sign in to comment.