package buildfile import ( "bufio" "errors" "fmt" "github.com/xdrm-brackets/nix-amer/internal/clifmt" "github.com/xdrm-brackets/nix-amer/internal/instruction" "io" "regexp" "strings" "time" ) // 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), } // 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 inst, err := instruction.Parse(line) if err != nil { return nil, LineError{l, err} } // 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]) } // 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 } } }