nix-amer/internal/buildfile/reader.go

212 lines
4.6 KiB
Go
Raw Normal View History

package buildfile
import (
"bufio"
"errors"
"fmt"
2018-11-13 12:10:55 +00:00
"github.com/xdrm-brackets/nix-amer/internal/clifmt"
"github.com/xdrm-brackets/nix-amer/internal/instruction"
"io"
"regexp"
"strings"
"time"
)
2018-11-11 18:01:00 +00:00
// ErrNullContext is raised when the given context is nil
var ErrNullContext = errors.New("null context")
// ErrNoParent is raised when there is an instruction but has no parent section
var ErrNoParent = errors.New("missing parent section")
// Reader is the buildfile reader
type Reader struct {
// Context is the linux distribution-specified execution context (package manager, service manager, etc)
Context *instruction.ExecutionContext
// Content is the instruction list
Content map[string]*[]instruction.T
}
type execStatus struct {
name string
start time.Time
stop time.Time
stopped bool
err error
}
type tableSection struct {
name string
instructions []execStatus
}
var reSection = regexp.MustCompile(`(?m)^\[\s*([a-z0-9_-]+)\s*\]$`)
// NewReader creates a new reader for the specified build file and linux distribution
func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader, error) {
// fail on null context
if ctx == nil {
return nil, ErrNullContext
}
r := &Reader{
Context: ctx,
Content: make(map[string]*[]instruction.T),
}
2018-11-09 21:37:05 +00:00
// add each line as instruction
l, reader := 0, bufio.NewReader(buildfile)
eof := false
var section *[]instruction.T = nil // current section
for {
l++
if eof {
break
}
// 1. read line until end
line, err := reader.ReadString('\n')
if err == io.EOF {
if len(line) > 0 {
eof = true
} else {
break
}
} else if err != nil {
return nil, LineError{l, err}
}
line = strings.Trim(line, " \t\r\n")
// 2. ignore newline & comments
if len(line) < 1 || strings.ContainsAny(line[0:1], "#;") {
continue
}
// 3. section
if match := reSection.FindStringSubmatch(line); len(match) > 1 {
// already in section
sec := make([]instruction.T, 0)
section = &sec
r.Content[match[1]] = section
continue
}
// 4. fail if no parent section
if section == nil {
return nil, ErrNoParent
}
// 5. create instruction
2018-11-09 21:37:05 +00:00
inst, err := instruction.Parse(line)
if err != nil {
return nil, LineError{l, err}
}
2018-11-09 21:37:05 +00:00
// add to list
*section = append(*section, inst)
}
return r, nil
}
// Execute the current buildfile instruction by instruction
func (r *Reader) Execute() error {
// 1. update package list
// err := r.Context.PackageManager.Fetch()
// if err != nil {
// return fmt.Errorf("cannot fetch packages | %s", err)
// }
// // 2. upgrade packages
// err = r.Context.PackageManager.Upgrade()
// if err != nil {
// return fmt.Errorf("cannot upgrade | %s", err)
// }
refresh := make(chan bool, 1)
// 1. create status table
table := make([]tableSection, 0)
for secname, sec := range r.Content {
tableSec := tableSection{
name: secname,
instructions: make([]execStatus, len(*sec), len(*sec)+1),
}
// for each instruction
for i, inst := range *sec {
tableSec.instructions[i].name = inst.Raw()
tableSec.instructions[i].start = time.Now()
}
table = append(table, tableSec)
go execSection(sec, *r.Context, refresh, &table[len(table)-1])
2018-11-10 12:20:10 +00:00
}
// 2. create status updater
go status(refresh, table)
time.Sleep(time.Second * 10)
close(refresh)
return nil
}
func execSection(section *[]instruction.T, ctx instruction.ExecutionContext, refresher chan<- bool, tsec *tableSection) {
for i, inst := range *section {
_, err := inst.Exec(ctx)
tsec.instructions[i].stop = time.Now()
tsec.instructions[i].stopped = true
tsec.instructions[i].err = err
refresher <- true
}
}
func status(refresher chan bool, table []tableSection) {
fmt.Printf("status\n")
remain := false
for <-refresher {
// 1. clean screen
fmt.Printf("\033[H\033[2J")
// 2. for each section
for _, sec := range table {
fmt.Printf("\n[ %s ]\n", sec.name)
// 3. for each instruction
for i, inst := range sec.instructions {
if !inst.stopped {
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.name)))
fmt.Printf("%s\n", clifmt.Color(33, "processing"))
remain = true
continue
}
if inst.err != nil {
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.name)))
fmt.Printf("%s\n", clifmt.Color(31, inst.err.Error()))
continue
} else {
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(34, inst.name)))
fmt.Printf("%ss\n", clifmt.Color(32, fmt.Sprintf("%.2f", inst.stop.Sub(inst.start).Seconds())))
}
}
}
// 4. close refresher if no remaining task
if !remain {
break
}
}
}