Added 'each-row' and 'to-upper' commands

This commit is contained in:
Leon Mika 2022-10-05 11:49:37 +11:00
parent 5424a6b927
commit 3ee6cbf514
4 changed files with 137 additions and 13 deletions

View file

@ -55,6 +55,8 @@ Others:
| `}` | Increase cell width |
| `/` | Search for cell matching regular expression |
| `n` | Find next cell matching search |
| `y` | Copy cell value |
| `p` | Paste cell value |
| `:` | Enter command |
## Commands
@ -62,7 +64,7 @@ Others:
Commands can be entered by pressing `:` and typing in the command or alias.
| Command | Alias | Description |
|:----------------|:-----------|:------------------------|
|:----------------------|:-----------|:------------------------|
| `save` | `w` | Save the current file. |
| `quit` | `q` | Quit the application without saving changes. |
| `save-and-quit` | `wq` | Save the current file and quit the application. |
@ -70,3 +72,4 @@ Commands can be entered by pressing `:` and typing in the command or alias.
| `open-right` | | Insert a new column to the right of the currently selected column. |
| `delete-row` | | Delete the currently selected row. |
| `delete-column` | | Delete the currently selected column. |

View file

@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/lmika/shellwords"
@ -67,12 +68,16 @@ func (cm *CommandMapping) Eval(ctx *CommandContext, expr string) error {
return nil
}
cmd := cm.Commands[toks[0]]
if cmd != nil {
return cmd.Do(ctx.WithArgs(toks[1:]))
return cm.Invoke(ctx, toks[0], toks[1:])
}
return fmt.Errorf("no such command: %v", expr)
func (cm *CommandMapping) Invoke(ctx *CommandContext, name string, args []string) error {
cmd := cm.Commands[name]
if cmd != nil {
return cmd.Do(ctx.WithArgs(args))
}
return fmt.Errorf("no such command: %v", name)
}
// Registers the standard view navigation commands. These commands require the frame
@ -345,8 +350,65 @@ func (cm *CommandMapping) RegisterViewCommands() {
return nil
})
cm.Define("to-upper", "Convert cell value to uppercase", "", func(ctx *CommandContext) error {
grid := ctx.Frame().Grid()
cellX, cellY := grid.CellPosition()
// TODO: allow ranges
if _, isRwModel := ctx.ModelVC().Model().(RWModel); !isRwModel {
return errors.New("Model is read-only")
}
currentValue := ctx.ModelVC().Model().CellValue(cellY, cellX)
newValue := strings.ToUpper(currentValue)
if err := ctx.ModelVC().SetCellValue(cellY, cellX, newValue); err != nil {
return err
}
return nil
})
cm.Define("each-row", "Executes the command for each row in the column", "", func(ctx *CommandContext) error {
if len(ctx.args) != 1 {
return errors.New("Sub-command required")
}
grid := ctx.Frame().Grid()
rows, _ := ctx.ModelVC().Model().Dimensions()
cellX, cellY := grid.CellPosition()
defer grid.MoveTo(cellX, cellY)
subCommand := ctx.args
for r := 0; r < rows; r++ {
grid.MoveTo(cellX, r)
if err := ctx.Session().Commands.Invoke(ctx, subCommand[0], subCommand[1:]); err != nil {
return fmt.Errorf("at [%d, %d]: %v", cellX, r, err)
}
}
return nil
})
cm.Define("save", "Save current file", "", func(ctx *CommandContext) error {
wSource, isWSource := ctx.Session().Source.(WritableModelSource)
var source ModelSource
if len(ctx.args) >= 2 {
targetCodecName := ctx.args[0]
codecBuilder, hasCodec := codecModelSourceBuilders[targetCodecName]
if !hasCodec {
return fmt.Errorf("unrecognsed codec: %v", targetCodecName)
}
targetFilename := ctx.args[1]
source = codecBuilder(targetFilename)
} else {
source = ctx.Session().Source
}
wSource, isWSource := source.(WritableModelSource)
if !isWSource {
return fmt.Errorf("model is not writable")
}
@ -356,6 +418,7 @@ func (cm *CommandMapping) RegisterViewCommands() {
}
ctx.Frame().Message("Wrote " + wSource.String())
ctx.Session().Source = wSource
return nil
})

View file

@ -46,4 +46,7 @@ var codecModelSourceBuilders = map[string]codecModelSourceBuilder{
"tsv": func(filename string) ModelSource {
return NewCsvFileModelSource(filename, CsvFileModelSourceOptions{Comma: '\t'})
},
"jira": func(filename string) ModelSource {
return JiraTableModelSource{Filename: filename, Header: true}
},
}

View file

@ -2,9 +2,12 @@ package main
import (
"encoding/csv"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// ModelSource is a source of models. At a minimum, it must be able to read models.
@ -108,3 +111,55 @@ func (s CsvFileModelSource) Write(m Model) error {
return f.Close()
}
type JiraTableModelSource struct {
Filename string
Header bool
}
func (s JiraTableModelSource) String() string {
return filepath.Base(s.Filename)
}
func (JiraTableModelSource) Read() (Model, error) {
return nil, errors.New("read not supported yet")
}
func (s JiraTableModelSource) Write(m Model) error {
f, err := os.Create(s.Filename)
if err != nil {
return err
}
defer f.Close()
rows, cols := m.Dimensions()
line := new(strings.Builder)
for r := 0; r < rows; r++ {
sep := "|"
if r == 0 && s.Header {
sep = "||"
}
line.Reset()
line.WriteString(sep)
line.WriteRune(' ')
for c := 0; c < cols; c++ {
if c >= 1 {
line.WriteRune(' ')
line.WriteString(sep)
line.WriteRune(' ')
}
line.WriteString(m.CellValue(r, c))
}
line.WriteRune(' ')
line.WriteString(sep)
if _, err := fmt.Fprintln(f, line.String()); err != nil {
return err
}
}
return nil
}