add parallelism with sections | todo: add [pre] section executed before all

This commit is contained in:
Adrien Marquès 2018-11-14 11:07:46 +01:00
parent 7e8b45d750
commit 5fd41cae67
2 changed files with 127 additions and 27 deletions

View File

@ -7,6 +7,7 @@ import (
"github.com/xdrm-brackets/nix-amer/internal/clifmt" "github.com/xdrm-brackets/nix-amer/internal/clifmt"
"github.com/xdrm-brackets/nix-amer/internal/instruction" "github.com/xdrm-brackets/nix-amer/internal/instruction"
"io" "io"
"regexp"
"strings" "strings"
"time" "time"
) )
@ -14,14 +15,31 @@ import (
// ErrNullContext is raised when the given context is nil // ErrNullContext is raised when the given context is nil
var ErrNullContext = errors.New("null context") 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 // Reader is the buildfile reader
type Reader struct { type Reader struct {
// Context is the linux distribution-specified execution context (package manager, service manager, etc) // Context is the linux distribution-specified execution context (package manager, service manager, etc)
Context *instruction.ExecutionContext Context *instruction.ExecutionContext
// Content is the instruction list // Content is the instruction list
Content []instruction.T 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 // NewReader creates a new reader for the specified build file and linux distribution
func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader, error) { func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader, error) {
@ -32,20 +50,20 @@ func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader,
r := &Reader{ r := &Reader{
Context: ctx, Context: ctx,
Content: make([]instruction.T, 0), Content: make(map[string]*[]instruction.T),
} }
// add each line as instruction // add each line as instruction
l, reader := 0, bufio.NewReader(buildfile) l, reader := 0, bufio.NewReader(buildfile)
eof := false eof := false
var section *[]instruction.T = nil // current section
for { for {
l++
if eof { if eof {
break break
} }
l++ // 1. read line until end
// read line until end
line, err := reader.ReadString('\n') line, err := reader.ReadString('\n')
if err == io.EOF { if err == io.EOF {
if len(line) > 0 { if len(line) > 0 {
@ -58,19 +76,34 @@ func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader,
} }
line = strings.Trim(line, " \t\r\n") line = strings.Trim(line, " \t\r\n")
// ignore newline & comments // 2. ignore newline & comments
if len(line) < 1 || strings.ContainsAny(line[0:1], "[#;") { if len(line) < 1 || strings.ContainsAny(line[0:1], "#;") {
continue continue
} }
// turn into instruction // 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
inst, err := instruction.Parse(line) inst, err := instruction.Parse(line)
if err != nil { if err != nil {
return nil, LineError{l, err} return nil, LineError{l, err}
} }
// add to list // add to list
r.Content = append(r.Content, inst) *section = append(*section, inst)
} }
@ -91,26 +124,88 @@ func (r *Reader) Execute() error {
// if err != nil { // if err != nil {
// return fmt.Errorf("cannot upgrade | %s", err) // return fmt.Errorf("cannot upgrade | %s", err)
// } // }
refresh := make(chan bool, 1)
// 3. exec each instruction // 1. create status table
for i, inst := range r.Content { table := make([]tableSection, 0)
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.Raw()))) for secname, sec := range r.Content {
fmt.Printf("%s", clifmt.Color(33, "processing")) tableSec := tableSection{
name: secname,
start := time.Now() instructions: make([]execStatus, len(*sec), len(*sec)+1),
_, err := inst.Exec(*r.Context)
if err != nil {
fmt.Printf("\r")
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.Raw())))
fmt.Printf("%s \n", clifmt.Color(31, err.Error()))
continue
} else {
fmt.Printf("\r")
clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(34, inst.Raw())))
fmt.Printf("%ss \n", clifmt.Color(32, fmt.Sprintf("%.2f", time.Now().Sub(start).Seconds())))
} }
// 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])
} }
// 2. create status updater
go status(refresh, table)
time.Sleep(time.Second * 10)
close(refresh)
return nil 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
}
}
}

View File

@ -30,7 +30,12 @@ func main() {
// 2. parse buildfile // 2. parse buildfile
instructions, err := buildfile.NewReader(ctx, bfreader) instructions, err := buildfile.NewReader(ctx, bfreader)
if err != nil { if err != nil {
fmt.Printf("%s%s\n", bf, err) if _, ok := err.(buildfile.LineError); ok {
fmt.Printf("line error\n")
fmt.Printf("%s%s\n", bf, err.Error())
} else {
fmt.Printf("%s\n", clifmt.Warn(err.Error()))
}
return return
} }