mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Move status table to cron package (#7370)
This commit is contained in:
		
				
					committed by
					
						
						techknowlogick
					
				
			
			
				
	
			
			
			
						parent
						
							760c473896
						
					
				
				
					commit
					d2958d9f46
				
			
							
								
								
									
										234
									
								
								vendor/github.com/gogits/cron/parser.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										234
									
								
								vendor/github.com/gogits/cron/parser.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,234 +0,0 @@
 | 
			
		||||
package cron
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Parse returns a new crontab schedule representing the given spec.
 | 
			
		||||
// It returns a descriptive error if the spec is not valid.
 | 
			
		||||
//
 | 
			
		||||
// It accepts
 | 
			
		||||
//   - Full crontab specs, e.g. "* * * * * ?"
 | 
			
		||||
//   - Descriptors, e.g. "@midnight", "@every 1h30m"
 | 
			
		||||
func Parse(spec string) (_ Schedule, err error) {
 | 
			
		||||
	// Convert panics into errors
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if recovered := recover(); recovered != nil {
 | 
			
		||||
			err = fmt.Errorf("%v", recovered)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	if spec[0] == '@' {
 | 
			
		||||
		return parseDescriptor(spec), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Split on whitespace.  We require 5 or 6 fields.
 | 
			
		||||
	// (second) (minute) (hour) (day of month) (month) (day of week, optional)
 | 
			
		||||
	fields := strings.Fields(spec)
 | 
			
		||||
	if len(fields) != 5 && len(fields) != 6 {
 | 
			
		||||
		log.Panicf("Expected 5 or 6 fields, found %d: %s", len(fields), spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If a sixth field is not provided (DayOfWeek), then it is equivalent to star.
 | 
			
		||||
	if len(fields) == 5 {
 | 
			
		||||
		fields = append(fields, "*")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	schedule := &SpecSchedule{
 | 
			
		||||
		Second: getField(fields[0], seconds),
 | 
			
		||||
		Minute: getField(fields[1], minutes),
 | 
			
		||||
		Hour:   getField(fields[2], hours),
 | 
			
		||||
		Dom:    getField(fields[3], dom),
 | 
			
		||||
		Month:  getField(fields[4], months),
 | 
			
		||||
		Dow:    getField(fields[5], dow),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return schedule, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getField returns an Int with the bits set representing all of the times that
 | 
			
		||||
// the field represents.  A "field" is a comma-separated list of "ranges".
 | 
			
		||||
func getField(field string, r bounds) uint64 {
 | 
			
		||||
	// list = range {"," range}
 | 
			
		||||
	var bits uint64
 | 
			
		||||
	ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
 | 
			
		||||
	for _, expr := range ranges {
 | 
			
		||||
		bits |= getRange(expr, r)
 | 
			
		||||
	}
 | 
			
		||||
	return bits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getRange returns the bits indicated by the given expression:
 | 
			
		||||
//   number | number "-" number [ "/" number ]
 | 
			
		||||
func getRange(expr string, r bounds) uint64 {
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		start, end, step uint
 | 
			
		||||
		rangeAndStep     = strings.Split(expr, "/")
 | 
			
		||||
		lowAndHigh       = strings.Split(rangeAndStep[0], "-")
 | 
			
		||||
		singleDigit      = len(lowAndHigh) == 1
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	var extra_star uint64
 | 
			
		||||
	if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
 | 
			
		||||
		start = r.min
 | 
			
		||||
		end = r.max
 | 
			
		||||
		extra_star = starBit
 | 
			
		||||
	} else {
 | 
			
		||||
		start = parseIntOrName(lowAndHigh[0], r.names)
 | 
			
		||||
		switch len(lowAndHigh) {
 | 
			
		||||
		case 1:
 | 
			
		||||
			end = start
 | 
			
		||||
		case 2:
 | 
			
		||||
			end = parseIntOrName(lowAndHigh[1], r.names)
 | 
			
		||||
		default:
 | 
			
		||||
			log.Panicf("Too many hyphens: %s", expr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch len(rangeAndStep) {
 | 
			
		||||
	case 1:
 | 
			
		||||
		step = 1
 | 
			
		||||
	case 2:
 | 
			
		||||
		step = mustParseInt(rangeAndStep[1])
 | 
			
		||||
 | 
			
		||||
		// Special handling: "N/step" means "N-max/step".
 | 
			
		||||
		if singleDigit {
 | 
			
		||||
			end = r.max
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		log.Panicf("Too many slashes: %s", expr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if start < r.min {
 | 
			
		||||
		log.Panicf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
 | 
			
		||||
	}
 | 
			
		||||
	if end > r.max {
 | 
			
		||||
		log.Panicf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
 | 
			
		||||
	}
 | 
			
		||||
	if start > end {
 | 
			
		||||
		log.Panicf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
 | 
			
		||||
	}
 | 
			
		||||
	if step == 0 {
 | 
			
		||||
		log.Panicf("Step of range should be a positive number: %s", expr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return getBits(start, end, step) | extra_star
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseIntOrName returns the (possibly-named) integer contained in expr.
 | 
			
		||||
func parseIntOrName(expr string, names map[string]uint) uint {
 | 
			
		||||
	if names != nil {
 | 
			
		||||
		if namedInt, ok := names[strings.ToLower(expr)]; ok {
 | 
			
		||||
			return namedInt
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return mustParseInt(expr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// mustParseInt parses the given expression as an int or panics.
 | 
			
		||||
func mustParseInt(expr string) uint {
 | 
			
		||||
	num, err := strconv.Atoi(expr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Panicf("Failed to parse int from %s: %s", expr, err)
 | 
			
		||||
	}
 | 
			
		||||
	if num < 0 {
 | 
			
		||||
		log.Panicf("Negative number (%d) not allowed: %s", num, expr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return uint(num)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getBits sets all bits in the range [min, max], modulo the given step size.
 | 
			
		||||
func getBits(min, max, step uint) uint64 {
 | 
			
		||||
	var bits uint64
 | 
			
		||||
 | 
			
		||||
	// If step is 1, use shifts.
 | 
			
		||||
	if step == 1 {
 | 
			
		||||
		return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Else, use a simple loop.
 | 
			
		||||
	for i := min; i <= max; i += step {
 | 
			
		||||
		bits |= 1 << i
 | 
			
		||||
	}
 | 
			
		||||
	return bits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// all returns all bits within the given bounds.  (plus the star bit)
 | 
			
		||||
func all(r bounds) uint64 {
 | 
			
		||||
	return getBits(r.min, r.max, 1) | starBit
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseDescriptor returns a pre-defined schedule for the expression, or panics
 | 
			
		||||
// if none matches.
 | 
			
		||||
func parseDescriptor(spec string) Schedule {
 | 
			
		||||
	switch spec {
 | 
			
		||||
	case "@yearly", "@annually":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   1 << hours.min,
 | 
			
		||||
			Dom:    1 << dom.min,
 | 
			
		||||
			Month:  1 << months.min,
 | 
			
		||||
			Dow:    all(dow),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case "@monthly":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   1 << hours.min,
 | 
			
		||||
			Dom:    1 << dom.min,
 | 
			
		||||
			Month:  all(months),
 | 
			
		||||
			Dow:    all(dow),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case "@weekly":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   1 << hours.min,
 | 
			
		||||
			Dom:    all(dom),
 | 
			
		||||
			Month:  all(months),
 | 
			
		||||
			Dow:    1 << dow.min,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case "@daily", "@midnight":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   1 << hours.min,
 | 
			
		||||
			Dom:    all(dom),
 | 
			
		||||
			Month:  all(months),
 | 
			
		||||
			Dow:    all(dow),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case "@hourly":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   all(hours),
 | 
			
		||||
			Dom:    all(dom),
 | 
			
		||||
			Month:  all(months),
 | 
			
		||||
			Dow:    all(dow),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const every = "@every "
 | 
			
		||||
	if strings.HasPrefix(spec, every) {
 | 
			
		||||
		duration, err := time.ParseDuration(spec[len(every):])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Panicf("Failed to parse duration %s: %s", spec, err)
 | 
			
		||||
		}
 | 
			
		||||
		return Every(duration)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	log.Panicf("Unrecognized descriptor: %s", spec)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								vendor/github.com/gogits/cron/.gitignore → vendor/github.com/gogs/cron/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										0
									
								
								vendor/github.com/gogits/cron/.gitignore → vendor/github.com/gogs/cron/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
								
								
									
										0
									
								
								vendor/github.com/gogits/cron/.travis.yml → vendor/github.com/gogs/cron/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										0
									
								
								vendor/github.com/gogits/cron/.travis.yml → vendor/github.com/gogs/cron/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
								
								
									
										0
									
								
								vendor/github.com/gogits/cron/LICENSE → vendor/github.com/gogs/cron/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										0
									
								
								vendor/github.com/gogits/cron/LICENSE → vendor/github.com/gogs/cron/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
								
								
									
										0
									
								
								vendor/github.com/gogits/cron/README.md → vendor/github.com/gogs/cron/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										0
									
								
								vendor/github.com/gogits/cron/README.md → vendor/github.com/gogs/cron/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
								
								
									
										94
									
								
								vendor/github.com/gogits/cron/cron.go → vendor/github.com/gogs/cron/cron.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										94
									
								
								vendor/github.com/gogits/cron/cron.go → vendor/github.com/gogs/cron/cron.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -1,5 +1,3 @@
 | 
			
		||||
// This library implements a cron spec parser and runner.  See the README for
 | 
			
		||||
// more details.
 | 
			
		||||
package cron
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@@ -19,6 +17,7 @@ type Cron struct {
 | 
			
		||||
	snapshot chan []*Entry
 | 
			
		||||
	running  bool
 | 
			
		||||
	ErrorLog *log.Logger
 | 
			
		||||
	location *time.Location
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Job is an interface for submitted cron jobs.
 | 
			
		||||
@@ -74,8 +73,13 @@ func (s byTime) Less(i, j int) bool {
 | 
			
		||||
	return s[i].Next.Before(s[j].Next)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New returns a new Cron job runner.
 | 
			
		||||
// New returns a new Cron job runner, in the Local time zone.
 | 
			
		||||
func New() *Cron {
 | 
			
		||||
	return NewWithLocation(time.Now().Location())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewWithLocation returns a new Cron job runner.
 | 
			
		||||
func NewWithLocation(location *time.Location) *Cron {
 | 
			
		||||
	return &Cron{
 | 
			
		||||
		entries:  nil,
 | 
			
		||||
		add:      make(chan *Entry),
 | 
			
		||||
@@ -83,6 +87,7 @@ func New() *Cron {
 | 
			
		||||
		snapshot: make(chan []*Entry),
 | 
			
		||||
		running:  false,
 | 
			
		||||
		ErrorLog: nil,
 | 
			
		||||
		location: location,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -131,6 +136,11 @@ func (c *Cron) Entries() []*Entry {
 | 
			
		||||
	return c.entrySnapshot()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Location gets the time zone location
 | 
			
		||||
func (c *Cron) Location() *time.Location {
 | 
			
		||||
	return c.location
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Start the cron scheduler in its own go-routine, or no-op if already started.
 | 
			
		||||
func (c *Cron) Start() {
 | 
			
		||||
	if c.running {
 | 
			
		||||
@@ -140,6 +150,15 @@ func (c *Cron) Start() {
 | 
			
		||||
	go c.run()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run the cron scheduler, or no-op if already running.
 | 
			
		||||
func (c *Cron) Run() {
 | 
			
		||||
	if c.running {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	c.running = true
 | 
			
		||||
	c.run()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c *Cron) runWithRecovery(j Job) {
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if r := recover(); r != nil {
 | 
			
		||||
@@ -152,11 +171,11 @@ func (c *Cron) runWithRecovery(j Job) {
 | 
			
		||||
	j.Run()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run the scheduler.. this is private just due to the need to synchronize
 | 
			
		||||
// Run the scheduler. this is private just due to the need to synchronize
 | 
			
		||||
// access to the 'running' state variable.
 | 
			
		||||
func (c *Cron) run() {
 | 
			
		||||
	// Figure out the next activation times for each entry.
 | 
			
		||||
	now := time.Now().Local()
 | 
			
		||||
	now := c.now()
 | 
			
		||||
	for _, entry := range c.entries {
 | 
			
		||||
		entry.Next = entry.Schedule.Next(now)
 | 
			
		||||
	}
 | 
			
		||||
@@ -165,45 +184,47 @@ func (c *Cron) run() {
 | 
			
		||||
		// Determine the next entry to run.
 | 
			
		||||
		sort.Sort(byTime(c.entries))
 | 
			
		||||
 | 
			
		||||
		var effective time.Time
 | 
			
		||||
		var timer *time.Timer
 | 
			
		||||
		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
 | 
			
		||||
			// If there are no entries yet, just sleep - it still handles new entries
 | 
			
		||||
			// and stop requests.
 | 
			
		||||
			effective = now.AddDate(10, 0, 0)
 | 
			
		||||
			timer = time.NewTimer(100000 * time.Hour)
 | 
			
		||||
		} else {
 | 
			
		||||
			effective = c.entries[0].Next
 | 
			
		||||
			timer = time.NewTimer(c.entries[0].Next.Sub(now))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		timer := time.NewTimer(effective.Sub(now))
 | 
			
		||||
		select {
 | 
			
		||||
		case now = <-timer.C:
 | 
			
		||||
			// Run every entry whose next time was this effective time.
 | 
			
		||||
			for _, e := range c.entries {
 | 
			
		||||
				if e.Next != effective {
 | 
			
		||||
					break
 | 
			
		||||
		for {
 | 
			
		||||
			select {
 | 
			
		||||
			case now = <-timer.C:
 | 
			
		||||
				now = now.In(c.location)
 | 
			
		||||
				// Run every entry whose next time was less than now
 | 
			
		||||
				for _, e := range c.entries {
 | 
			
		||||
					if e.Next.After(now) || e.Next.IsZero() {
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					go c.runWithRecovery(e.Job)
 | 
			
		||||
					e.ExecTimes++
 | 
			
		||||
					e.Prev = e.Next
 | 
			
		||||
					e.Next = e.Schedule.Next(now)
 | 
			
		||||
				}
 | 
			
		||||
				go c.runWithRecovery(e.Job)
 | 
			
		||||
				e.ExecTimes++
 | 
			
		||||
				e.Prev = e.Next
 | 
			
		||||
				e.Next = e.Schedule.Next(now)
 | 
			
		||||
 | 
			
		||||
			case newEntry := <-c.add:
 | 
			
		||||
				timer.Stop()
 | 
			
		||||
				now = c.now()
 | 
			
		||||
				newEntry.Next = newEntry.Schedule.Next(now)
 | 
			
		||||
				c.entries = append(c.entries, newEntry)
 | 
			
		||||
 | 
			
		||||
			case <-c.snapshot:
 | 
			
		||||
				c.snapshot <- c.entrySnapshot()
 | 
			
		||||
				continue
 | 
			
		||||
 | 
			
		||||
			case <-c.stop:
 | 
			
		||||
				timer.Stop()
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
 | 
			
		||||
		case newEntry := <-c.add:
 | 
			
		||||
			c.entries = append(c.entries, newEntry)
 | 
			
		||||
			newEntry.Next = newEntry.Schedule.Next(time.Now().Local())
 | 
			
		||||
 | 
			
		||||
		case <-c.snapshot:
 | 
			
		||||
			c.snapshot <- c.entrySnapshot()
 | 
			
		||||
 | 
			
		||||
		case <-c.stop:
 | 
			
		||||
			timer.Stop()
 | 
			
		||||
			return
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 'now' should be updated after newEntry and snapshot cases.
 | 
			
		||||
		now = time.Now().Local()
 | 
			
		||||
		timer.Stop()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -241,3 +262,8 @@ func (c *Cron) entrySnapshot() []*Entry {
 | 
			
		||||
	}
 | 
			
		||||
	return entries
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// now returns current time in c location
 | 
			
		||||
func (c *Cron) now() time.Time {
 | 
			
		||||
	return time.Now().In(c.location)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								vendor/github.com/gogits/cron/doc.go → vendor/github.com/gogs/cron/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/gogits/cron/doc.go → vendor/github.com/gogs/cron/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -84,16 +84,16 @@ You may use one of several pre-defined schedules in place of a cron expression.
 | 
			
		||||
 | 
			
		||||
Intervals
 | 
			
		||||
 | 
			
		||||
You may also schedule a job to execute at fixed intervals.  This is supported by
 | 
			
		||||
formatting the cron spec like this:
 | 
			
		||||
You may also schedule a job to execute at fixed intervals, starting at the time it's added 
 | 
			
		||||
or cron is run. This is supported by formatting the cron spec like this:
 | 
			
		||||
 | 
			
		||||
    @every <duration>
 | 
			
		||||
 | 
			
		||||
where "duration" is a string accepted by time.ParseDuration
 | 
			
		||||
(http://golang.org/pkg/time/#ParseDuration).
 | 
			
		||||
 | 
			
		||||
For example, "@every 1h30m10s" would indicate a schedule that activates every
 | 
			
		||||
1 hour, 30 minutes, 10 seconds.
 | 
			
		||||
For example, "@every 1h30m10s" would indicate a schedule that activates immediately,
 | 
			
		||||
and then every 1 hour, 30 minutes, 10 seconds.
 | 
			
		||||
 | 
			
		||||
Note: The interval does not take the job runtime into account.  For example,
 | 
			
		||||
if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes,
 | 
			
		||||
							
								
								
									
										380
									
								
								vendor/github.com/gogs/cron/parser.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								vendor/github.com/gogs/cron/parser.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,380 @@
 | 
			
		||||
package cron
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Configuration options for creating a parser. Most options specify which
 | 
			
		||||
// fields should be included, while others enable features. If a field is not
 | 
			
		||||
// included the parser will assume a default value. These options do not change
 | 
			
		||||
// the order fields are parse in.
 | 
			
		||||
type ParseOption int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Second      ParseOption = 1 << iota // Seconds field, default 0
 | 
			
		||||
	Minute                              // Minutes field, default 0
 | 
			
		||||
	Hour                                // Hours field, default 0
 | 
			
		||||
	Dom                                 // Day of month field, default *
 | 
			
		||||
	Month                               // Month field, default *
 | 
			
		||||
	Dow                                 // Day of week field, default *
 | 
			
		||||
	DowOptional                         // Optional day of week field, default *
 | 
			
		||||
	Descriptor                          // Allow descriptors such as @monthly, @weekly, etc.
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var places = []ParseOption{
 | 
			
		||||
	Second,
 | 
			
		||||
	Minute,
 | 
			
		||||
	Hour,
 | 
			
		||||
	Dom,
 | 
			
		||||
	Month,
 | 
			
		||||
	Dow,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var defaults = []string{
 | 
			
		||||
	"0",
 | 
			
		||||
	"0",
 | 
			
		||||
	"0",
 | 
			
		||||
	"*",
 | 
			
		||||
	"*",
 | 
			
		||||
	"*",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// A custom Parser that can be configured.
 | 
			
		||||
type Parser struct {
 | 
			
		||||
	options   ParseOption
 | 
			
		||||
	optionals int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Creates a custom Parser with custom options.
 | 
			
		||||
//
 | 
			
		||||
//  // Standard parser without descriptors
 | 
			
		||||
//  specParser := NewParser(Minute | Hour | Dom | Month | Dow)
 | 
			
		||||
//  sched, err := specParser.Parse("0 0 15 */3 *")
 | 
			
		||||
//
 | 
			
		||||
//  // Same as above, just excludes time fields
 | 
			
		||||
//  subsParser := NewParser(Dom | Month | Dow)
 | 
			
		||||
//  sched, err := specParser.Parse("15 */3 *")
 | 
			
		||||
//
 | 
			
		||||
//  // Same as above, just makes Dow optional
 | 
			
		||||
//  subsParser := NewParser(Dom | Month | DowOptional)
 | 
			
		||||
//  sched, err := specParser.Parse("15 */3")
 | 
			
		||||
//
 | 
			
		||||
func NewParser(options ParseOption) Parser {
 | 
			
		||||
	optionals := 0
 | 
			
		||||
	if options&DowOptional > 0 {
 | 
			
		||||
		options |= Dow
 | 
			
		||||
		optionals++
 | 
			
		||||
	}
 | 
			
		||||
	return Parser{options, optionals}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse returns a new crontab schedule representing the given spec.
 | 
			
		||||
// It returns a descriptive error if the spec is not valid.
 | 
			
		||||
// It accepts crontab specs and features configured by NewParser.
 | 
			
		||||
func (p Parser) Parse(spec string) (Schedule, error) {
 | 
			
		||||
	if len(spec) == 0 {
 | 
			
		||||
		return nil, fmt.Errorf("Empty spec string")
 | 
			
		||||
	}
 | 
			
		||||
	if spec[0] == '@' && p.options&Descriptor > 0 {
 | 
			
		||||
		return parseDescriptor(spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Figure out how many fields we need
 | 
			
		||||
	max := 0
 | 
			
		||||
	for _, place := range places {
 | 
			
		||||
		if p.options&place > 0 {
 | 
			
		||||
			max++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	min := max - p.optionals
 | 
			
		||||
 | 
			
		||||
	// Split fields on whitespace
 | 
			
		||||
	fields := strings.Fields(spec)
 | 
			
		||||
 | 
			
		||||
	// Validate number of fields
 | 
			
		||||
	if count := len(fields); count < min || count > max {
 | 
			
		||||
		if min == max {
 | 
			
		||||
			return nil, fmt.Errorf("Expected exactly %d fields, found %d: %s", min, count, spec)
 | 
			
		||||
		}
 | 
			
		||||
		return nil, fmt.Errorf("Expected %d to %d fields, found %d: %s", min, max, count, spec)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fill in missing fields
 | 
			
		||||
	fields = expandFields(fields, p.options)
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	field := func(field string, r bounds) uint64 {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
		var bits uint64
 | 
			
		||||
		bits, err = getField(field, r)
 | 
			
		||||
		return bits
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		second     = field(fields[0], seconds)
 | 
			
		||||
		minute     = field(fields[1], minutes)
 | 
			
		||||
		hour       = field(fields[2], hours)
 | 
			
		||||
		dayofmonth = field(fields[3], dom)
 | 
			
		||||
		month      = field(fields[4], months)
 | 
			
		||||
		dayofweek  = field(fields[5], dow)
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &SpecSchedule{
 | 
			
		||||
		Second: second,
 | 
			
		||||
		Minute: minute,
 | 
			
		||||
		Hour:   hour,
 | 
			
		||||
		Dom:    dayofmonth,
 | 
			
		||||
		Month:  month,
 | 
			
		||||
		Dow:    dayofweek,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func expandFields(fields []string, options ParseOption) []string {
 | 
			
		||||
	n := 0
 | 
			
		||||
	count := len(fields)
 | 
			
		||||
	expFields := make([]string, len(places))
 | 
			
		||||
	copy(expFields, defaults)
 | 
			
		||||
	for i, place := range places {
 | 
			
		||||
		if options&place > 0 {
 | 
			
		||||
			expFields[i] = fields[n]
 | 
			
		||||
			n++
 | 
			
		||||
		}
 | 
			
		||||
		if n == count {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return expFields
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var standardParser = NewParser(
 | 
			
		||||
	Minute | Hour | Dom | Month | Dow | Descriptor,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ParseStandard returns a new crontab schedule representing the given standardSpec
 | 
			
		||||
// (https://en.wikipedia.org/wiki/Cron). It differs from Parse requiring to always
 | 
			
		||||
// pass 5 entries representing: minute, hour, day of month, month and day of week,
 | 
			
		||||
// in that order. It returns a descriptive error if the spec is not valid.
 | 
			
		||||
//
 | 
			
		||||
// It accepts
 | 
			
		||||
//   - Standard crontab specs, e.g. "* * * * ?"
 | 
			
		||||
//   - Descriptors, e.g. "@midnight", "@every 1h30m"
 | 
			
		||||
func ParseStandard(standardSpec string) (Schedule, error) {
 | 
			
		||||
	return standardParser.Parse(standardSpec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var defaultParser = NewParser(
 | 
			
		||||
	Second | Minute | Hour | Dom | Month | DowOptional | Descriptor,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Parse returns a new crontab schedule representing the given spec.
 | 
			
		||||
// It returns a descriptive error if the spec is not valid.
 | 
			
		||||
//
 | 
			
		||||
// It accepts
 | 
			
		||||
//   - Full crontab specs, e.g. "* * * * * ?"
 | 
			
		||||
//   - Descriptors, e.g. "@midnight", "@every 1h30m"
 | 
			
		||||
func Parse(spec string) (Schedule, error) {
 | 
			
		||||
	return defaultParser.Parse(spec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getField returns an Int with the bits set representing all of the times that
 | 
			
		||||
// the field represents or error parsing field value.  A "field" is a comma-separated
 | 
			
		||||
// list of "ranges".
 | 
			
		||||
func getField(field string, r bounds) (uint64, error) {
 | 
			
		||||
	var bits uint64
 | 
			
		||||
	ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' })
 | 
			
		||||
	for _, expr := range ranges {
 | 
			
		||||
		bit, err := getRange(expr, r)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return bits, err
 | 
			
		||||
		}
 | 
			
		||||
		bits |= bit
 | 
			
		||||
	}
 | 
			
		||||
	return bits, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getRange returns the bits indicated by the given expression:
 | 
			
		||||
//   number | number "-" number [ "/" number ]
 | 
			
		||||
// or error parsing range.
 | 
			
		||||
func getRange(expr string, r bounds) (uint64, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		start, end, step uint
 | 
			
		||||
		rangeAndStep     = strings.Split(expr, "/")
 | 
			
		||||
		lowAndHigh       = strings.Split(rangeAndStep[0], "-")
 | 
			
		||||
		singleDigit      = len(lowAndHigh) == 1
 | 
			
		||||
		err              error
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	var extra uint64
 | 
			
		||||
	if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" {
 | 
			
		||||
		start = r.min
 | 
			
		||||
		end = r.max
 | 
			
		||||
		extra = starBit
 | 
			
		||||
	} else {
 | 
			
		||||
		start, err = parseIntOrName(lowAndHigh[0], r.names)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		switch len(lowAndHigh) {
 | 
			
		||||
		case 1:
 | 
			
		||||
			end = start
 | 
			
		||||
		case 2:
 | 
			
		||||
			end, err = parseIntOrName(lowAndHigh[1], r.names)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			return 0, fmt.Errorf("Too many hyphens: %s", expr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch len(rangeAndStep) {
 | 
			
		||||
	case 1:
 | 
			
		||||
		step = 1
 | 
			
		||||
	case 2:
 | 
			
		||||
		step, err = mustParseInt(rangeAndStep[1])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Special handling: "N/step" means "N-max/step".
 | 
			
		||||
		if singleDigit {
 | 
			
		||||
			end = r.max
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return 0, fmt.Errorf("Too many slashes: %s", expr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if start < r.min {
 | 
			
		||||
		return 0, fmt.Errorf("Beginning of range (%d) below minimum (%d): %s", start, r.min, expr)
 | 
			
		||||
	}
 | 
			
		||||
	if end > r.max {
 | 
			
		||||
		return 0, fmt.Errorf("End of range (%d) above maximum (%d): %s", end, r.max, expr)
 | 
			
		||||
	}
 | 
			
		||||
	if start > end {
 | 
			
		||||
		return 0, fmt.Errorf("Beginning of range (%d) beyond end of range (%d): %s", start, end, expr)
 | 
			
		||||
	}
 | 
			
		||||
	if step == 0 {
 | 
			
		||||
		return 0, fmt.Errorf("Step of range should be a positive number: %s", expr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return getBits(start, end, step) | extra, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseIntOrName returns the (possibly-named) integer contained in expr.
 | 
			
		||||
func parseIntOrName(expr string, names map[string]uint) (uint, error) {
 | 
			
		||||
	if names != nil {
 | 
			
		||||
		if namedInt, ok := names[strings.ToLower(expr)]; ok {
 | 
			
		||||
			return namedInt, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return mustParseInt(expr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// mustParseInt parses the given expression as an int or returns an error.
 | 
			
		||||
func mustParseInt(expr string) (uint, error) {
 | 
			
		||||
	num, err := strconv.Atoi(expr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, fmt.Errorf("Failed to parse int from %s: %s", expr, err)
 | 
			
		||||
	}
 | 
			
		||||
	if num < 0 {
 | 
			
		||||
		return 0, fmt.Errorf("Negative number (%d) not allowed: %s", num, expr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return uint(num), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getBits sets all bits in the range [min, max], modulo the given step size.
 | 
			
		||||
func getBits(min, max, step uint) uint64 {
 | 
			
		||||
	var bits uint64
 | 
			
		||||
 | 
			
		||||
	// If step is 1, use shifts.
 | 
			
		||||
	if step == 1 {
 | 
			
		||||
		return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Else, use a simple loop.
 | 
			
		||||
	for i := min; i <= max; i += step {
 | 
			
		||||
		bits |= 1 << i
 | 
			
		||||
	}
 | 
			
		||||
	return bits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// all returns all bits within the given bounds.  (plus the star bit)
 | 
			
		||||
func all(r bounds) uint64 {
 | 
			
		||||
	return getBits(r.min, r.max, 1) | starBit
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseDescriptor returns a predefined schedule for the expression, or error if none matches.
 | 
			
		||||
func parseDescriptor(descriptor string) (Schedule, error) {
 | 
			
		||||
	switch descriptor {
 | 
			
		||||
	case "@yearly", "@annually":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   1 << hours.min,
 | 
			
		||||
			Dom:    1 << dom.min,
 | 
			
		||||
			Month:  1 << months.min,
 | 
			
		||||
			Dow:    all(dow),
 | 
			
		||||
		}, nil
 | 
			
		||||
 | 
			
		||||
	case "@monthly":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   1 << hours.min,
 | 
			
		||||
			Dom:    1 << dom.min,
 | 
			
		||||
			Month:  all(months),
 | 
			
		||||
			Dow:    all(dow),
 | 
			
		||||
		}, nil
 | 
			
		||||
 | 
			
		||||
	case "@weekly":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   1 << hours.min,
 | 
			
		||||
			Dom:    all(dom),
 | 
			
		||||
			Month:  all(months),
 | 
			
		||||
			Dow:    1 << dow.min,
 | 
			
		||||
		}, nil
 | 
			
		||||
 | 
			
		||||
	case "@daily", "@midnight":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   1 << hours.min,
 | 
			
		||||
			Dom:    all(dom),
 | 
			
		||||
			Month:  all(months),
 | 
			
		||||
			Dow:    all(dow),
 | 
			
		||||
		}, nil
 | 
			
		||||
 | 
			
		||||
	case "@hourly":
 | 
			
		||||
		return &SpecSchedule{
 | 
			
		||||
			Second: 1 << seconds.min,
 | 
			
		||||
			Minute: 1 << minutes.min,
 | 
			
		||||
			Hour:   all(hours),
 | 
			
		||||
			Dom:    all(dom),
 | 
			
		||||
			Month:  all(months),
 | 
			
		||||
			Dow:    all(dow),
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const every = "@every "
 | 
			
		||||
	if strings.HasPrefix(descriptor, every) {
 | 
			
		||||
		duration, err := time.ParseDuration(descriptor[len(every):])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to parse duration %s: %s", descriptor, err)
 | 
			
		||||
		}
 | 
			
		||||
		return Every(duration), nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil, fmt.Errorf("Unrecognized descriptor: %s", descriptor)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								vendor/github.com/gogits/cron/spec.go → vendor/github.com/gogs/cron/spec.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/gogits/cron/spec.go → vendor/github.com/gogs/cron/spec.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -151,7 +151,6 @@ func dayMatches(s *SpecSchedule, t time.Time) bool {
 | 
			
		||||
		domMatch bool = 1<<uint(t.Day())&s.Dom > 0
 | 
			
		||||
		dowMatch bool = 1<<uint(t.Weekday())&s.Dow > 0
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if s.Dom&starBit > 0 || s.Dow&starBit > 0 {
 | 
			
		||||
		return domMatch && dowMatch
 | 
			
		||||
	}
 | 
			
		||||
		Reference in New Issue
	
	Block a user