diff --git a/internal/buildfile/reader.go b/internal/buildfile/reader.go index c2811ba..6f0ea9e 100644 --- a/internal/buildfile/reader.go +++ b/internal/buildfile/reader.go @@ -7,6 +7,7 @@ import ( "github.com/xdrm-brackets/nix-amer/internal/clifmt" "github.com/xdrm-brackets/nix-amer/internal/instruction" "io" + "regexp" "strings" "time" ) @@ -14,14 +15,31 @@ import ( // 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 []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 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{ Context: ctx, - Content: make([]instruction.T, 0), + Content: make(map[string]*[]instruction.T), } // 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 } - l++ - - // read line until end + // 1. read line until end line, err := reader.ReadString('\n') if err == io.EOF { if len(line) > 0 { @@ -58,19 +76,34 @@ func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader, } line = strings.Trim(line, " \t\r\n") - // ignore newline & comments - if len(line) < 1 || strings.ContainsAny(line[0:1], "[#;") { + // 2. ignore newline & comments + if len(line) < 1 || strings.ContainsAny(line[0:1], "#;") { 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) if err != nil { return nil, LineError{l, err} } // 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 { // return fmt.Errorf("cannot upgrade | %s", err) // } + refresh := make(chan bool, 1) - // 3. exec each instruction - for i, inst := range r.Content { - clifmt.Align(fmt.Sprintf("(%d) %s", i, clifmt.Color(0, inst.Raw()))) - fmt.Printf("%s", clifmt.Color(33, "processing")) - - start := time.Now() - - _, 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()))) + // 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]) + } + // 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 + } + + } + +} diff --git a/main.go b/main.go index 85fe240..cb6cddd 100644 --- a/main.go +++ b/main.go @@ -30,7 +30,12 @@ func main() { // 2. parse buildfile instructions, err := buildfile.NewReader(ctx, bfreader) 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 }