Skip to content

Commit

Permalink
Add support for labels in template files/directories
Browse files Browse the repository at this point in the history
change readfile.go: new ReadLines function to read x lines from a
source.

change model.go: new in memory db for storing modeline options and
labels.

add scan.go: scan template directories for options and labels, to
be used for the future support of listing and filtering by labels.
  • Loading branch information
dexterp committed Apr 18, 2023
1 parent 08e889b commit 9524b08
Show file tree
Hide file tree
Showing 16 changed files with 578 additions and 33 deletions.
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"request": "launch",
"mode": "debug",
"program": "${file}",
"cwd": "${workspaceFolder}",
"envFile": "${workspaceFolder}/.env"
},
{
Expand Down
121 changes: 104 additions & 17 deletions internal/resources/file/readfile.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,118 @@
package file

import (
"bufio"
"bytes"
"errors"
"io"
"os"
)

// ReadLines If src != nil, ReadFile converts src to a []byte if possible;
// otherwise it returns an error. If src == nil, readSource returns
// the result of reading the file specified by filename.
func ReadLines(filename string, src any, lines int) ([]byte, error) {
var err error
if src == nil {
src, err = os.Open(filename)
if err != nil {
return nil, err
}
}
switch t := src.(type) {
case string:
return str2Lines(t, lines)
case []byte:
return str2Lines(string(t), lines)
case *bytes.Buffer:
return buf2Lines(t, lines)
case io.Reader:
return rdr2Lines(t, lines)
}
return nil, errors.New("invalid source")
}

func str2Lines(str string, lines int) ([]byte, error) {
c := 0
for i := 0; i < len(str); i++ {
if str[i] == '\n' {
c++
}
if c == lines {
return []byte(str[:i+1]), nil
}
}
return []byte(str), nil
}

func buf2Lines(buf *bytes.Buffer, lines int) ([]byte, error) {
var out []rune
c := 0
for {
r, _, err := buf.ReadRune()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}
out = append(out, r)
if r == '\n' {
c++
}
if c == lines {
return []byte(string(out)), nil
}
}
if len(out) > 0 {
return []byte(string(out)), nil
}
return buf.Bytes(), nil
}

func rdr2Lines(rdr io.Reader, lines int) ([]byte, error) {
buf := bufio.NewReader(rdr)
var out []rune
c := 0
for {
r, _, err := buf.ReadRune()
if err == io.EOF {
return []byte(string(out)), nil
} else if err != nil {
return nil, err
}
out = append(out, r)
if r == '\n' {
c++
}
if c == lines {
return []byte(string(out)), nil
}
}
}

// ReadFile If src != nil, ReadFile converts src to a []byte if possible;
// otherwise it returns an error. If src == nil, readSource returns
// the result of reading the file specified by filename.
func ReadFile(filename string, src any) ([]byte, error) {
if src != nil {
switch s := src.(type) {
case string:
return []byte(s), nil
case []byte:
return s, nil
case *bytes.Buffer:
// is io.Reader, but src is already available in []byte form
if s != nil {
return s.Bytes(), nil
}
case io.Reader:
return io.ReadAll(s)
}
return nil, errors.New("invalid source")
}
return os.ReadFile(filename)
var err error
if src == nil {
src, err = os.Open(filename)
if err != nil {
return nil, err
}
}
switch s := src.(type) {
case string:
return []byte(s), nil
case []byte:
return s, nil
case *bytes.Buffer:
// is io.Reader, but src is already available in []byte form
if s != nil {
return s.Bytes(), nil
}
case io.Reader:
return io.ReadAll(s)
}
return nil, errors.New("invalid source")
}
57 changes: 56 additions & 1 deletion internal/resources/file/readfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
_ "embed"
"io"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -15,6 +16,60 @@ var expected = readFileText

var readFile = "readfile.txt"

func TestReadLines_Byte_unlimited(t *testing.T) {
actual, err := ReadLines(readFile, readFileText, 10)
assert.NoError(t, err)
assert.Equal(t, 5, strings.Count(string(actual), "\n"))
}

func TestReadLines_String_unlimited(t *testing.T) {
actual, err := ReadLines(readFile, string(readFileText), 10)
assert.NoError(t, err)
assert.Equal(t, 5, strings.Count(string(actual), "\n"))
}

func TestReadLines_BytesBuffer_unlimited(t *testing.T) {
buf := new(bytes.Buffer)
buf.Write(readFileText)
actual, err := ReadLines(readFile, buf, 10)
assert.NoError(t, err)
assert.Equal(t, 5, strings.Count(string(actual), "\n"))
}

func TestReadLines_IoReader_unlimited(t *testing.T) {
rdr := bytes.NewReader(readFileText)
actual, err := ReadLines(readFile, io.Reader(rdr), 10)
assert.NoError(t, err)
assert.Equal(t, 5, strings.Count(string(actual), "\n"))
}

func TestReadLines_Byte(t *testing.T) {
actual, err := ReadLines(readFile, readFileText, 2)
assert.NoError(t, err)
assert.Equal(t, 2, strings.Count(string(actual), "\n"))
}

func TestReadLines_String(t *testing.T) {
actual, err := ReadLines(readFile, string(readFileText), 2)
assert.NoError(t, err)
assert.Equal(t, 2, strings.Count(string(actual), "\n"))
}

func TestReadLines_BytesBuffer(t *testing.T) {
buf := new(bytes.Buffer)
buf.Write(readFileText)
actual, err := ReadLines(readFile, buf, 2)
assert.NoError(t, err)
assert.Equal(t, 2, strings.Count(string(actual), "\n"))
}

func TestReadLines_IoReader(t *testing.T) {
rdr := bytes.NewReader(readFileText)
actual, err := ReadLines(readFile, io.Reader(rdr), 2)
assert.NoError(t, err)
assert.Equal(t, 2, strings.Count(string(actual), "\n"))
}

func TestReadFile_Byte(t *testing.T) {
actual, err := ReadFile(readFile, readFileText)
assert.NoError(t, err)
Expand All @@ -35,7 +90,7 @@ func TestReadFile_BytesBuffer(t *testing.T) {
assert.Equal(t, expected, actual)
}

func TestReadFile_IoWriter(t *testing.T) {
func TestReadFile_IoReader(t *testing.T) {
rdr := bytes.NewReader(readFileText)
actual, err := ReadFile(readFile, io.Reader(rdr))
assert.NoError(t, err)
Expand Down
62 changes: 61 additions & 1 deletion internal/resources/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

//
// Models
// Model local storage
//

// Repo a set of templates
Expand Down Expand Up @@ -65,6 +65,43 @@ type Versions struct {
Template Template `gorm:"foreignKey:TemplateID"`
}

//
// Model in memory
//

// Base
type Base struct {
gorm.Model
ID uint `gorm:"primaryKey;not null"`
Base string `gorm:"index:,unique"`
File []File `gorm:"foreignKey:BaseID"`
}

// File
type File struct {
gorm.Model
ID uint `gorm:"primaryKey;not null"`
File string `gorm:"index:idx_file_template,unique"`
BaseID uint `gorm:"index:idx_file_template,unique"`
Option []Option `gorm:"many2many:file_option"`
Label []Label `gorm:"many2many:file_label"`
}

// Option
type Option struct {
gorm.Model
ID uint `gorm:"primaryKey;not null"`
Option string `gorm:"index:,unique"`
File []File `gorm:"many2many:file_option"`
}

// Label
type Label struct {
ID uint `gorm:"primaryKey;not null"`
Label string `gorm:"index:,unique"`
File []File `gorm:"many2many:file_label"`
}

//
//
//
Expand Down Expand Up @@ -107,3 +144,26 @@ func CreateModel(opts *Options) (db *gorm.DB) {

return db
}

// CreateModelTemporary
func CreateModelTemporary(opts Options) (db *gorm.DB) {
dia := sqlite.Open(opts.File)
db, err := gorm.Open(dia, &gorm.Config{
NamingStrategy: schema.NamingStrategy{
SingularTable: true,
},
})

errs.FatalF("Can not initialize in memory database: %v", err)

err = db.AutoMigrate(
&Base{},
&File{},
&Label{},
&Option{},
)

errs.FatalF("Can not create in memory database: %v", err)

return db
}
4 changes: 4 additions & 0 deletions internal/resources/model/motel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ func TestCreateModel(t *testing.T) {
File: path,
})
}

func TestCreateModelMemory(t *testing.T) {
model.CreateModelTemporary(model.Options{File: "file::memory:"})
}
38 changes: 27 additions & 11 deletions internal/resources/modeline/modeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,28 @@ import (
func Parse(path string, input any, lines int) (*ModeLine, error) {
empty := true
ml := ModeLine{
options: map[string]any{},
labels: map[string]any{},
options_uniq: map[string]any{},
labels_uniq: map[string]any{},
}
src, err := file.ReadFile(path, input)
src, err := file.ReadLines(path, input, lines)
if err != nil {
return nil, err
}
items := parser.Parse(path, string(src), lines)
for _, i := range items {
if i.Type == parser.OPTION {
ml.options[i.Value] = struct{}{}
empty = false
if _, ok := ml.options_uniq[i.Value]; !ok {
ml.options = append(ml.options, i.Value)
ml.options_uniq[i.Value] = struct{}{}
empty = false
}
}
if i.Type == parser.LABEL {
ml.labels[i.Value] = struct{}{}
empty = false
if _, ok := ml.labels_uniq[i.Value]; !ok {
ml.labels = append(ml.labels, i.Value)
ml.labels_uniq[i.Value] = struct{}{}
empty = false
}
}
}
if empty {
Expand All @@ -34,16 +40,26 @@ func Parse(path string, input any, lines int) (*ModeLine, error) {
}

type ModeLine struct {
options map[string]any
labels map[string]any
options []string
options_uniq map[string]any
labels []string
labels_uniq map[string]any
}

func (m ModeLine) GetOptions() []string {
return m.options
}

func (m ModeLine) Option(option string) bool {
_, ok := m.options[option]
_, ok := m.options_uniq[option]
return ok
}

func (m ModeLine) GetLabel() []string {
return m.labels
}

func (m ModeLine) Label(label string) bool {
_, ok := m.labels[label]
_, ok := m.labels_uniq[label]
return ok
}
Loading

0 comments on commit 9524b08

Please sign in to comment.