Added support for TSV files

This uses a similar parser as CSV files, except configured for tabs.
Have also fixed the configured parser to ignore column counts.
This commit is contained in:
Leon Mika 2022-06-03 10:29:06 +10:00
parent 82face0010
commit 975955236f
3 changed files with 52 additions and 16 deletions

View file

@ -15,10 +15,14 @@ go get github.com/lmika/ted
## Usage ## Usage
``` ```
ted <csvfile> ted [FLAGS] FILE
``` ```
Can either be a new CSV file, or an existing CSV file. Flags:
- `-c <codec>` the format that the file is in. Either `csv` or `tsv` files are supported. Default is `csv`
File can either be a new file, or an existing file.
TED is similar to Vim in that it is modal. After opening a file, the editor starts off in view mode, which permits navigating around. TED is similar to Vim in that it is modal. After opening a file, the editor starts off in view mode, which permits navigating around.

22
main.go
View file

@ -1,13 +1,14 @@
package main package main
import ( import (
"github.com/lmika/ted/ui"
"flag" "flag"
"fmt" "fmt"
"github.com/lmika/ted/ui"
"os" "os"
) )
func main() { func main() {
var flagCodec = flag.String("c", "csv", "file codec to use")
flag.Parse() flag.Parse()
if flag.NArg() == 0 { if flag.NArg() == 0 {
fmt.Fprintln(os.Stderr, "usage: ted FILENAME") fmt.Fprintln(os.Stderr, "usage: ted FILENAME")
@ -20,8 +21,14 @@ func main() {
} }
defer uiManager.Close() defer uiManager.Close()
codecBuilder, hasCodec := codecModelSourceBuilders[*flagCodec]
if !hasCodec {
fmt.Fprintf(os.Stderr, "unrecognised codec: %v", *flagCodec)
os.Exit(1)
}
frame := NewFrame(uiManager) frame := NewFrame(uiManager)
session := NewSession(uiManager, frame, CsvFileModelSource{flag.Arg(0)}) session := NewSession(uiManager, frame, codecBuilder(flag.Arg(0)))
session.LoadFromSource() session.LoadFromSource()
uiManager.SetRootComponent(frame.RootComponent()) uiManager.SetRootComponent(frame.RootComponent())
@ -29,3 +36,14 @@ func main() {
uiManager.Loop() uiManager.Loop()
} }
type codecModelSourceBuilder func(filename string) ModelSource
var codecModelSourceBuilders = map[string]codecModelSourceBuilder{
"csv": func(filename string) ModelSource {
return NewCsvFileModelSource(filename, CsvFileModelSourceOptions{Comma: ','})
},
"tsv": func(filename string) ModelSource {
return NewCsvFileModelSource(filename, CsvFileModelSourceOptions{Comma: '\t'})
},
}

View file

@ -1,10 +1,10 @@
package main package main
import ( import (
"path/filepath"
"os"
"encoding/csv" "encoding/csv"
"io" "io"
"os"
"path/filepath"
) )
// ModelSource is a source of models. At a minimum, it must be able to read models. // ModelSource is a source of models. At a minimum, it must be able to read models.
@ -26,22 +26,34 @@ type WritableModelSource interface {
// A model source backed by a CSV file // A model source backed by a CSV file
type CsvFileModelSource struct { type CsvFileModelSource struct {
Filename string filename string
options CsvFileModelSourceOptions
}
type CsvFileModelSourceOptions struct {
Comma rune
}
func NewCsvFileModelSource(filename string, options CsvFileModelSourceOptions) CsvFileModelSource {
return CsvFileModelSource{
filename: filename,
options: options,
}
} }
// Describes the source // Describes the source
func (s CsvFileModelSource) String() string { func (s CsvFileModelSource) String() string {
return filepath.Base(s.Filename) return filepath.Base(s.filename)
} }
// Read the model from the given source // Read the model from the given source
func (s CsvFileModelSource) Read() (Model, error) { func (s CsvFileModelSource) Read() (Model, error) {
// Check if the file exists. If not, return an empty model // Check if the file exists. If not, return an empty model
if _, err := os.Stat(s.Filename); os.IsNotExist(err) { if _, err := os.Stat(s.filename); os.IsNotExist(err) {
return NewSingleCellStdModel(), nil return NewSingleCellStdModel(), nil
} }
f, err := os.Open(s.Filename) f, err := os.Open(s.filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -49,12 +61,13 @@ func (s CsvFileModelSource) Read() (Model, error) {
model := new(StdModel) model := new(StdModel)
r := csv.NewReader(f) r := csv.NewReader(f)
r.Comma = s.options.Comma
r.FieldsPerRecord = -1
for { for {
record, err := r.Read() record, err := r.Read()
if err == io.EOF { if err == io.EOF {
break break
} } else if err != nil {
if err != nil {
return nil, err return nil, err
} }
@ -66,19 +79,20 @@ func (s CsvFileModelSource) Read() (Model, error) {
} }
func (s CsvFileModelSource) Write(m Model) error { func (s CsvFileModelSource) Write(m Model) error {
f, err := os.Create(s.Filename) f, err := os.Create(s.filename)
if err != nil { if err != nil {
return err return err
} }
w := csv.NewWriter(f) w := csv.NewWriter(f)
w.Comma = s.options.Comma
rows, cols := m.Dimensions() rows, cols := m.Dimensions()
for r := 0; r < rows; r++ { for r := 0; r < rows; r++ {
record := make([]string, cols) // Reuse the record slice record := make([]string, cols) // Reuse the record slice
for c := 0; c < cols; c++ { for c := 0; c < cols; c++ {
record[c] = m.CellValue(r, c) record[c] = m.CellValue(r, c)
} }
if err := w.Write(record); err != nil { if err := w.Write(record); err != nil {
f.Close() f.Close()
@ -93,4 +107,4 @@ func (s CsvFileModelSource) Write(m Model) error {
} }
return f.Close() return f.Close()
} }