Skip to content

Commit

Permalink
Merge pull request #223 from yohamta/feat/parsing-parameters
Browse files Browse the repository at this point in the history
Allow parameters field contains spaces inside each parameter
  • Loading branch information
yottahmd authored Jul 30, 2022
2 parents 78af441 + e1be9a6 commit 7a5eeca
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 152 deletions.
282 changes: 155 additions & 127 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"time"

"github.com/mattn/go-shellwords"
"github.com/robfig/cron/v3"
"github.com/yohamta/dagu/internal/constants"
"github.com/yohamta/dagu/internal/settings"
Expand Down Expand Up @@ -57,10 +58,7 @@ var EXTENSIONS = []string{".yaml", ".yml"}

func ReadConfig(file string) (string, error) {
b, err := os.ReadFile(file)
if err != nil {
return "", err
}
return string(b), nil
return string(b), err
}

func (c *Config) Init() {
Expand Down Expand Up @@ -160,181 +158,199 @@ type BuildConfigOptions struct {

type builder struct {
BuildConfigOptions
globalConfig *Config
}

type buildStep struct {
BuildFn func(def *configDefinition, cfg *Config) error
Headline bool
}

var cronParser = cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)

func (b *builder) buildFromDefinition(def *configDefinition, globalConfig *Config) (c *Config, err error) {
c = &Config{}
c.Init()
func (b *builder) buildFromDefinition(def *configDefinition, globalConfig *Config) (cfg *Config, err error) {
b.globalConfig = globalConfig

cfg = &Config{}
cfg.Init()

c.Name = def.Name
cfg.Name = def.Name
if def.Name != "" {
c.Name = def.Name
cfg.Name = def.Name
}
c.Group = def.Group
c.Description = def.Description
cfg.Group = def.Group
cfg.Description = def.Description
if def.MailOn != nil {
c.MailOn = &MailOn{
cfg.MailOn = &MailOn{
Failure: def.MailOn.Failure,
Success: def.MailOn.Success,
}
}
c.Delay = time.Second * time.Duration(def.DelaySec)
c.Tags = parseTags(def.Tags)
cfg.Delay = time.Second * time.Duration(def.DelaySec)
cfg.Tags = parseTags(def.Tags)

for _, bs := range []buildStep{
{
BuildFn: b.buildSchedule,
Headline: true,
},
{
BuildFn: b.buildEnvVariables,
},
{
BuildFn: b.buildLogdir,
},
{
BuildFn: b.buildParameters,
},
{
BuildFn: b.buildStepsFromDefinition,
},
{
BuildFn: b.buildHandlers,
},
{
BuildFn: b.buildConfig,
},
{
BuildFn: buildSmtpConfigFromDefinition,
},
{
BuildFn: buildErrorMailConfig,
},
{
BuildFn: buildInfoMailConfig,
},
} {
if (b.headOnly && bs.Headline) || !b.headOnly {
if err = bs.BuildFn(def, cfg); err != nil {
return
}
}
}

return cfg, nil
}

func (b *builder) buildSchedule(def *configDefinition, cfg *Config) (err error) {
switch (def.Schedule).(type) {
case string:
c.ScheduleExp = []string{def.Schedule.(string)}
cfg.ScheduleExp = []string{def.Schedule.(string)}
case []interface{}:
items := []string{}
for _, s := range def.Schedule.([]interface{}) {
if a, ok := s.(string); ok {
items = append(items, a)
} else {
return nil, fmt.Errorf("schedule must be a string or an array of strings")
return fmt.Errorf("schedule must be a string or an array of strings")
}
}
c.ScheduleExp = items
cfg.ScheduleExp = items
case nil:
default:
return nil, fmt.Errorf("invalid schedule type: %T", def.Schedule)
}
c.Schedule, err = parseSchedule(c.ScheduleExp)
if err != nil {
return nil, err
}

if b.headOnly {
return c, nil
}

env, err := b.loadVariables(def.Env, b.defaultEnv)
if err != nil {
return nil, err
return fmt.Errorf("invalid schedule type: %T", def.Schedule)
}
cfg.Schedule, err = parseSchedule(cfg.ScheduleExp)
return
}

c.Env = buildConfigEnv(env)
if globalConfig != nil {
for _, e := range globalConfig.Env {
key := strings.SplitN(e, "=", 2)[0]
if _, ok := env[key]; !ok {
c.Env = append(c.Env, e)
func (b *builder) buildEnvVariables(def *configDefinition, cfg *Config) (err error) {
var env map[string]string
env, err = b.loadVariables(def.Env, b.defaultEnv)
if err == nil {
cfg.Env = buildConfigEnv(env)
if b.globalConfig != nil {
for _, e := range b.globalConfig.Env {
key := strings.SplitN(e, "=", 2)[0]
if _, ok := env[key]; !ok {
cfg.Env = append(cfg.Env, e)
}
}
}
}
return
}

logDir, err := utils.ParseVariable(def.LogDir)
if err != nil {
return nil, err
}
c.LogDir = logDir
if def.HistRetentionDays != nil {
c.HistRetentionDays = *def.HistRetentionDays
}
func (b *builder) buildLogdir(def *configDefinition, cfg *Config) (err error) {
cfg.LogDir, err = utils.ParseVariable(def.LogDir)
return err
}

c.DefaultParams = def.Params
p := c.DefaultParams
func (b *builder) buildParameters(def *configDefinition, cfg *Config) (err error) {
cfg.DefaultParams = def.Params
p := cfg.DefaultParams
if b.parameters != "" {
p = b.parameters
}

var envs []string
c.Params, envs, err = b.parseParameters(p, !b.noEval)
if err != nil {
return nil, err
}
c.Env = append(c.Env, envs...)

c.Steps, err = b.buildStepsFromDefinition(c.Env, def.Steps)
if err != nil {
return nil, err
cfg.Params, envs, err = b.parseParameters(p, !b.noEval)
if err == nil {
cfg.Env = append(cfg.Env, envs...)
}
return
}

func (b *builder) buildHandlers(def *configDefinition, cfg *Config) (err error) {
if def.HandlerOn.Exit != nil {
def.HandlerOn.Exit.Name = constants.OnExit
c.HandlerOn.Exit, err = b.buildStep(c.Env, def.HandlerOn.Exit)
if err != nil {
return nil, err
if cfg.HandlerOn.Exit, err = b.buildStep(cfg.Env, def.HandlerOn.Exit); err != nil {
return err
}
}

if def.HandlerOn.Success != nil {
def.HandlerOn.Success.Name = constants.OnSuccess
c.HandlerOn.Success, err = b.buildStep(c.Env, def.HandlerOn.Success)
if err != nil {
return nil, err
if cfg.HandlerOn.Success, err = b.buildStep(cfg.Env, def.HandlerOn.Success); err != nil {
return
}
}

if def.HandlerOn.Failure != nil {
def.HandlerOn.Failure.Name = constants.OnFailure
c.HandlerOn.Failure, err = b.buildStep(c.Env, def.HandlerOn.Failure)
if err != nil {
return nil, err
if cfg.HandlerOn.Failure, err = b.buildStep(cfg.Env, def.HandlerOn.Failure); err != nil {
return
}
}

if def.HandlerOn.Cancel != nil {
def.HandlerOn.Cancel.Name = constants.OnCancel
c.HandlerOn.Cancel, err = b.buildStep(c.Env, def.HandlerOn.Cancel)
if err != nil {
return nil, err
if cfg.HandlerOn.Cancel, err = b.buildStep(cfg.Env, def.HandlerOn.Cancel); err != nil {
return
}
}
return nil
}

c.Smtp, err = buildSmtpConfigFromDefinition(def.Smtp)
if err != nil {
return nil, err
}
c.ErrorMail, err = buildMailConfigFromDefinition(def.ErrorMail)
if err != nil {
return nil, err
}
c.InfoMail, err = buildMailConfigFromDefinition(def.InfoMail)
if err != nil {
return nil, err
func (b *builder) buildConfig(def *configDefinition, cfg *Config) (err error) {
if def.HistRetentionDays != nil {
cfg.HistRetentionDays = *def.HistRetentionDays
}
c.Preconditions = loadPreCondition(def.Preconditions)
c.MaxActiveRuns = def.MaxActiveRuns
cfg.Preconditions = loadPreCondition(def.Preconditions)
cfg.MaxActiveRuns = def.MaxActiveRuns

if def.MaxCleanUpTimeSec != nil {
c.MaxCleanUpTime = time.Second * time.Duration(*def.MaxCleanUpTimeSec)
cfg.MaxCleanUpTime = time.Second * time.Duration(*def.MaxCleanUpTimeSec)
}

return c, nil
return nil
}

func (b *builder) parseParameters(value string, eval bool) (
params []string,
envs []string,
err error,
) {
separated := []string{}
i, j, f := 0, 1, false
parser := shellwords.NewParser()
parser.ParseBacktick = false
parser.ParseEnv = false

value = strings.TrimSpace(strings.TrimRight(strings.TrimLeft(value, "\""), "\""))
if len(value) > 0 {
if value[0] == '`' {
f = true
}
for {
if j == len(value) || (value[j] == ' ' && !f) {
separated = append(separated, value[i:j])
i = j + 1
j = i
} else if value[j] == '`' {
f = !f
}
if j >= len(value) {
break
}
j++
}
var parsed []string
parsed, err = parser.Parse(value)
if err != nil {
return
}

ret := []string{}
for i, v := range separated {
v = strings.TrimRight(strings.TrimLeft(v, "\""), "\"")
for i, v := range parsed {
if eval {
v, err = utils.ParseCommand(os.ExpandEnv(v))
if err != nil {
Expand Down Expand Up @@ -419,31 +435,17 @@ func (b *builder) loadVariables(strVariables interface{}, defaults map[string]st
return vars, nil
}

func buildSmtpConfigFromDefinition(def smtpConfigDef) (*SmtpConfig, error) {
smtp := &SmtpConfig{}
smtp.Host = def.Host
smtp.Port = def.Port
return smtp, nil
}

func buildMailConfigFromDefinition(def mailConfigDef) (*MailConfig, error) {
c := &MailConfig{}
c.From = def.From
c.To = def.To
c.Prefix = def.Prefix
return c, nil
}

func (b *builder) buildStepsFromDefinition(variables []string, stepDefs []*stepDef) ([]*Step, error) {
func (b *builder) buildStepsFromDefinition(def *configDefinition, cfg *Config) error {
ret := []*Step{}
for _, def := range stepDefs {
step, err := b.buildStep(variables, def)
for _, stepDef := range def.Steps {
step, err := b.buildStep(cfg.Env, stepDef)
if err != nil {
return nil, err
return err
}
ret = append(ret, step)
}
return ret, nil
cfg.Steps = ret
return nil
}

func (b *builder) buildStep(variables []string, def *stepDef) (*Step, error) {
Expand Down Expand Up @@ -487,6 +489,32 @@ func (b *builder) expandEnv(val string) string {
return os.ExpandEnv(val)
}

func buildSmtpConfigFromDefinition(def *configDefinition, cfg *Config) (err error) {
smtp := &SmtpConfig{}
smtp.Host = def.Smtp.Host
smtp.Port = def.Smtp.Port
cfg.Smtp = smtp
return nil
}

func buildErrorMailConfig(def *configDefinition, cfg *Config) (err error) {
cfg.ErrorMail, err = buildMailConfigFromDefinition(def.ErrorMail)
return
}

func buildInfoMailConfig(def *configDefinition, cfg *Config) (err error) {
cfg.InfoMail, err = buildMailConfigFromDefinition(def.InfoMail)
return
}

func buildMailConfigFromDefinition(def mailConfigDef) (*MailConfig, error) {
cfg := &MailConfig{}
cfg.From = def.From
cfg.To = def.To
cfg.Prefix = def.Prefix
return cfg, nil
}

func buildConfigEnv(vars map[string]string) []string {
ret := []string{}
for k, v := range vars {
Expand Down
Loading

0 comments on commit 7a5eeca

Please sign in to comment.