func appendData(r *recipe.Recipe, e *entity, line uint16) error {
	if e.info.Global {
		return applyGlobalOptions(r, e, line)
	}
	action := &recipe.Action{
		Name:      e.info.Keyword,
		Arguments: e.args,
		Negative:  e.isNegative,
		Line:      line,
	}
	lastCommand := r.Commands[len(r.Commands)-1]
	lastCommand.AddAction(action)
	return nil
}
appendData append data to recipe struct
package parser
import (
	"bufio"
	"fmt"
	"io"
	"os"
	"regexp"
	"strconv"
	"strings"
	"pkg.re/essentialkaos/ek.v12/fsutil"
	"pkg.re/essentialkaos/ek.v12/strutil"
	"github.com/essentialkaos/bibop/recipe"
)
type entity struct {
	info       recipe.TokenInfo
	args       []string
	tag        string
	isNegative bool
}
var tagRegex = regexp.MustCompile(`^command:([a-zA-Z_0-9]+)`)
func Parse(file string) (*recipe.Recipe, error) {
	err := checkRecipeFile(file)
	if err != nil {
		return nil, err
	}
	return parseRecipeFile(file)
}
func checkRecipeFile(file string) error {
	if !fsutil.CheckPerms("FR", file) {
		return fmt.Errorf("File %s doesn't exist or not readable", file)
	}
	if !fsutil.IsNonEmpty(file) {
		return fmt.Errorf("File %s is empty", file)
	}
	return nil
}
func parseRecipeFile(file string) (*recipe.Recipe, error) {
	fd, err := os.Open(file)
	if err != nil {
		return nil, err
	}
	defer fd.Close()
	reader := bufio.NewReader(fd)
	return parseRecipeData(file, reader)
}
func parseRecipeData(file string, reader io.Reader) (*recipe.Recipe, error) {
	var lineNum uint16
	result := recipe.NewRecipe(file)
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		lineNum++
		line := scanner.Text()
		if isUselessRecipeLine(line) {
			continue
		}
		e, err := parseLine(line)
		if err != nil {
			return nil, fmt.Errorf("Parsing error in line %d: %v", lineNum, err)
		}
		if !e.info.Global && len(result.Commands) == 0 {
			return nil, fmt.Errorf("Parsing error in line %d: keyword \"%s\" is not allowed there", lineNum, e.info.Keyword)
		}
		err = appendData(result, e, lineNum)
		if err != nil {
			return nil, fmt.Errorf("Parsing error in line %d: %v", lineNum, err)
		}
	}
	result.Dir, _ = os.Getwd()
	return result, nil
}
func parseLine(line string) (*entity, error) {
	var isGlobal bool
	if strings.HasPrefix(line, "  ") || strings.HasPrefix(line, "\t") {
		line = strings.TrimLeft(line, " \t")  
	} else {
		isGlobal = true
	}
	cmd := strutil.Fields(line)
	if len(cmd) == 0 {
		return nil, fmt.Errorf("Can't parse token data")
	}
	info := getTokenInfo(cmd[0])
	tag := extractTag(cmd[0])
	if info.Keyword == "" || info.Global != isGlobal {
		switch isGlobal {
		case true:
			return nil, fmt.Errorf("Global keyword \"%s\" is not supported", cmd[0])
		case false:
			return nil, fmt.Errorf("Keyword \"%s\" is not supported", cmd[0])
		}
	}
	isNegative := strings.HasPrefix(cmd[0], "!")
	if isNegative && !info.AllowNegative {
		return nil, fmt.Errorf("Action \"%s\" does not support negative results", cmd[0])
	}
	argsNum := len(cmd) - 1
	switch {
	case argsNum > info.MaxArgs:
		return nil, fmt.Errorf("Action \"%s\" has too many arguments (maximum is %d)", info.Keyword, info.MaxArgs)
	case argsNum < info.MinArgs:
		return nil, fmt.Errorf("Action \"%s\" has too few arguments (minimum is %d)", info.Keyword, info.MinArgs)
	}
	return &entity{info, cmd[1:], tag, isNegative}, nil
}