func garbageCollectSchedule(gid string) error {
	var jgz map[string]jobDetails
	err := db.Get(grontabConfiguration.BucketName, gid, &jgz)
	if err != nil {
		return errors.Wrap(err, "Error during garbage collection of potentially unused gid")
	}
	if len(jgz) == 0 {
		c.Remove(ugidTable[gid])
		delete(ugidTable, gid)
		db.Delete(grontabConfiguration.BucketName, gid)
	}
	return nil
}
it checks if a schedule is empty and in that case delete it
package grontab
import (
	"fmt"
	"io/ioutil"
	"log"
	"os/exec"
	"strings"
	"sync"
	"github.com/asdine/storm"
	"github.com/coreos/bbolt"
	"github.com/damdo/randid"
	"github.com/fatih/color"
	"github.com/pkg/errors"
	"github.com/wgliang/cron"
)
type Config struct {
	BucketName         string
	PersistencePath    string
	DisableParallelism bool
	HideBanner         bool
	TurnOffLogs        bool
}
type Job struct {
	ID      string
	Task    string
	Enabled bool
}
type jobDetails struct {
	Task    string
	Enabled bool
}
var (
	red    = color.New(color.FgRed, color.Bold).SprintFunc()
	yellow = color.New(color.FgYellow, color.Bold).SprintFunc()
	green  = color.New(color.FgGreen, color.Bold).SprintFunc()
	cyan   = color.New(color.FgCyan).SprintFunc()
)
var grontabConfiguration Config
var c *cron.Cron
var db = new(storm.DB)
var ugidTable = make(map[string]string)
var banner string = `
                                   __             __  
   ____ _   _____  ____    ____   / /_  ____ _   / /_ 
  / __  /  / ___/ / __ \  / __ \ / __/ / __  /  / __ \
 / /_/ /  / /    / /_/ / / / /  
 \__, /  /_/     \____/ /_/ /_/ \__/  \__,_/  /_.___/ 
/____/                                                
`
func Init(config Config) error {
	return initialize(config)
}
func Start() {
	start()
}
func Add(schedule string, job Job) (string, error) {
	return add(job.ID, schedule, jobDetails{Task: job.Task, Enabled: job.Enabled})
}
func Remove(id string) error {
	return remove(id)
}
func Update(schedule string, job Job) error {
	return update(job.ID, schedule, jobDetails{Task: job.Task, Enabled: job.Enabled})
}
func List() map[string][]Job {
	return list()
}
func Stop() {
	stop()
}
func initialize(config Config) error {
	grontabConfiguration = config
	if !grontabConfiguration.HideBanner {
		fmt.Printf("%s\n", banner)
	}
	if grontabConfiguration.TurnOffLogs {
		log.SetFlags(0)
		log.SetOutput(ioutil.Discard)
	}
	var err error
	db, err = storm.Open(grontabConfiguration.PersistencePath)
	if err != nil {
		return errors.Wrap(err, "Error Initializing grontab")
	}
	c = cron.New()
	keys, err2 := getKeys()
	if err2 != nil {
		log.Println("No elements in the Persistence Storage")
	} else {
		log.Println("Found Elements in the Persistence Storage, restarting them ...")
		for _, gid := range keys {
			var jg map[string]jobDetails
			err := db.Get(grontabConfiguration.BucketName, gid, &jg)
			if err != nil {
				log.Panic("Error Getting object from storage for gid: " + gid)
			}
			worker := workerFuncGen(gid)
			rid, err := randid.ID()
			if err != nil {
				return err
			}
			ugid := fmt.Sprintf("%s", rid)
			err = c.AddFunc(gid, worker, ugid)
			if err != nil {
				log.Println(err)
			}
			ugidTable[gid] = ugid
		}
	}
	return nil
}
func start() {
	c.Start()
}
func add(jid string, gid string, task jobDetails) (string, error) {
	var jg map[string]jobDetails
	err := db.Get(grontabConfiguration.BucketName, gid, &jg)
	if err != nil {
		jg = make(map[string]jobDetails)
		worker := workerFuncGen(gid)
		rid, err := randid.ID()
		if err != nil {
			return "", err
		}
		ugid := fmt.Sprintf("%s", rid)
		err = c.AddFunc(gid, worker, ugid)
		if err != nil {
			return "", errors.Wrap(err, "Error Adding schedule to grontab")
		}
		ugidTable[gid] = ugid
	}
	taskAlreadyExists := false
	var taskKey string
	for k, v := range jg {
		if v.Task == task.Task || k == jid {
			taskAlreadyExists = true
			taskKey = k
			break
		}
	}
	if !taskAlreadyExists {
		if jid == "" {
			rid, err := randid.ID()
			if err != nil {
				return "", err
			}
			jid = fmt.Sprintf("%s", rid)
		}
		jg[jid] = jobDetails{
			Task:    task.Task,
			Enabled: task.Enabled,
		}
		err := db.Set(grontabConfiguration.BucketName, gid, jg)
		if err != nil {
			return "", errors.Wrap(err, "Error Adding schedule to grontab persistent storage")
		}
		log.Printf(green("ADD JOB : {%s %s enabled:%t} to ['%s']"), jid, task.Task, task.Enabled, gid)
		return jid, nil
	}
	log.Printf("Job %s already Present at ['%s']\n", task.Task, gid)
	return taskKey, nil
}
func remove(jid string) error {
	gid, exists, err := find(jid)
	if err != nil {
		return err
	}
	if exists {
		var jg map[string]jobDetails
		err := db.Get(grontabConfiguration.BucketName, gid, &jg)
		if err != nil {
			log.Panic("Error Getting object from storage")
		}
		toBeDeletedJob := jg[jid]
		delete(jg, jid)
		err = db.Set(grontabConfiguration.BucketName, gid, jg)
		if err != nil {
			return errors.Wrap(err, "Unable to Remove job with jid: "+jid+" from grontab schedule gid "+gid)
		}
		err = garbageCollectSchedule(gid)
		if err != nil {
			return err
		}
		log.Printf(yellow("REM JOB : {%s %s enabled:%t} from ['%s']"), jid, toBeDeletedJob.Task, toBeDeletedJob.Enabled, gid)
	}
	return nil
}
func update(jid string, schedule string, task jobDetails) error {
	gid, exist, err := find(jid)
	if err != nil {
		return err
	}
	if exist {
		zjg := make(map[string]jobDetails)
		err := db.Get(grontabConfiguration.BucketName, gid, &zjg)
		if err != nil {
			return errors.Wrap(err, "Error Updating Job "+jid)
		}
		delete(zjg, jid)
		err = db.Delete(grontabConfiguration.BucketName, gid)
		if err != nil {
			return errors.Wrap(err, "Error Updating Job")
		}
		err = db.Set(grontabConfiguration.BucketName, gid, zjg)
		if err != nil {
			return errors.Wrap(err, "Error Updating Job")
		}
		njg := make(map[string]jobDetails)
		db.Get(grontabConfiguration.BucketName, schedule, &njg)
		njg[jid] = jobDetails{
			Task:    task.Task,
			Enabled: task.Enabled,
		}
		err = db.Set(grontabConfiguration.BucketName, schedule, njg)
		if err != nil {
			return errors.Wrap(err, "Error Updating Job")
		}
		err = garbageCollectSchedule(gid)
		if err != nil {
			return err
		}
		if schedule != gid {
			worker := workerFuncGen(schedule)
			rid, err := randid.ID()
			if err != nil {
				return err
			}
			ugid := fmt.Sprintf("%s", rid)
			c.AddFunc(schedule, worker, ugid)
			ugidTable[schedule] = ugid
		}
		log.Printf(yellow("UPD JOB : {%s %s enabled:%t} to ['%s']"), jid, task.Task, task.Enabled, schedule)
		return nil
	}
	return errors.New(red("ERR Grontab: job.ID: '" + jid + "' non provided or doesn't exists"))
}
func list() map[string][]Job {
	jobs := make(map[string][]Job)
	keys, err := getKeys()
	if err != nil {
		log.Println("No elements in the Persistence Storage")
	} else {
		for _, gid := range keys {
			var jg map[string]jobDetails
			err := db.Get(grontabConfiguration.BucketName, gid, &jg)
			if err != nil {
				log.Panic("Error Getting object from storage for gid: " + gid)
			}
			var scheduleJobs []Job
			for k, v := range jg {
				scheduleJobs = append(scheduleJobs, Job{ID: k, Task: v.Task, Enabled: v.Enabled})
			}
			jobs[gid] = scheduleJobs
		}
	}
	return jobs
}
func workerFuncGen(gid string) func() {
	return func() {
		rid, err := randid.ID()
		if err != nil {
			panic(err)
		}
		jobGroupID := fmt.Sprintf("%s", rid)
		log.Printf(green("RUNN JG(%s)[%s]"), jobGroupID, gid)
		var jg map[string]jobDetails
		err = db.Get(grontabConfiguration.BucketName, gid, &jg)
		if err != nil {
			log.Panic("Error Putting object in storage\n")
		}
		var jobWaitGroup sync.WaitGroup
		var taskWaitGroup sync.WaitGroup
		for jid, task := range jg {
			args := strings.Fields(task.Task)
			if task.Enabled {
				log.Printf(green("EXEC JG(%s)[%s][%s]: %s"), jobGroupID, gid, jid, task.Task)
				jobWaitGroup.Add(1)
				if grontabConfiguration.DisableParallelism {
					taskWaitGroup.Add(1)
				}
				go func() {
					cmdOut, err := exec.Command(args[0], args[1:]...).CombinedOutput()
					cleanOutput := strings.Replace(string(cmdOut), "\n", "", -1)
					if err != nil {
						log.Printf(red("Error executing: %s --> %s --> args: %#v\n"), task.Task, err, args)
					}
					log.Printf(
						cyan("OUTP JG(%s)[%s][%s]: %s"),
						jobGroupID,
						gid,
						jid,
						cleanOutput,
					)
					jobWaitGroup.Done()
					if grontabConfiguration.DisableParallelism {
						taskWaitGroup.Done()
					}
				}()
				if grontabConfiguration.DisableParallelism {
					taskWaitGroup.Wait()
				}
			}
		}
		jobWaitGroup.Wait()
		log.Printf(green("ENDD JG(%s)[%s]"), jobGroupID, gid)
	}
}
func stop() {
	c.Stop()
	db.Close()
}
func getKeys() ([]string, error) {
	var keys []string
	err := db.Bolt.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(grontabConfiguration.BucketName))
		if b == nil {
			return fmt.Errorf("No Storage bucket " + grontabConfiguration.BucketName + " found")
		}
		c := b.Cursor()
		for k, _ := c.First(); k != nil; k, _ = c.Next() {
			kstr := fmt.Sprintf("%s", k)
			if kstr != "__storm_metadata" {
				keys = append(keys, kstr)
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return keys, nil
}
func find(jid string) (string, bool, error) {
	keys, err := getKeys()
	if err != nil {
		return "", false, errors.Wrap(err, "Error finding Job")
	}
	var jg map[string]jobDetails
	for _, gid := range keys {
		err := db.Get(grontabConfiguration.BucketName, gid, &jg)
		if err != nil {
			log.Panic("Error Getting object from storage for gid: " + gid)
		}
		for k := range jg {
			if jid == k {
				return gid, true, nil
			}
		}
	}
	return "", false, nil
}