func (td *ToDoGenerator) Generate() ([]*ToDoComment, error) {
	matchesCount := 0
	totalFiles := 0
	err := filepath.Walk(td.root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.Mode().IsRegular() {
			return nil
		}
		totalFiles++
		if !td.Includes(path) {
			return nil
		}
		if td.Excludes(path) {
			return nil
		}
		anyMatch := false
		for _, f := range td.include {
			if f.MatchString(path) {
				anyMatch = true
				break
			}
		}
		if !anyMatch && len(td.include) > 0 {
			return nil
		}
		matchesCount++
		td.commentsWG.Add(1)
		go td.parseFile(path)
		return nil
	})
	if err != nil {
		return nil, err
	}
	log.Printf("Scanned files: %v", totalFiles)
	log.Printf("Matched files: %v", matchesCount)
	td.commentsWG.Wait()
	log.Printf("Found comments: %v", len(td.comments))
	return td.comments, nil
}
Generate is an entry point to comment generation
package tdglib
import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"errors"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"unicode"
	"github.com/zieckey/goini"
)
const (
	estimateEpsilon = 0.01
	minTitleWords   = 2
)
var (
	commentPrefixes        = [...]string{"TODO", "FIXME", "BUG", "HACK"}
	emptyRunes             = [...]rune{}
	categoryIniKey         = "category"
	issueIniKey            = "issue"
	estimateIniKey         = "estimate"
	authorIniKey           = "author"
	errCannotParseIni      = errors.New("Cannot parse ini properties")
	errCannotParseEstimate = errors.New("Cannot parse time estimate")
)
type ToDoComment struct {
	Type     string  `json:"type"`
	Title    string  `json:"title"`
	Body     string  `json:"body"`
	File     string  `json:"file"`
	Line     int     `json:"line"`
	Issue    int     `json:"issue,omitempty"`
	Author   string  `json:"author,omitempty"`
	Category string  `json:"category,omitempty"`
	Estimate float64 `json:"estimate,omitempty"`
}
type ToDoGenerator struct {
	root       string
	include    []*regexp.Regexp
	exclude    []*regexp.Regexp
	commentsWG sync.WaitGroup
	comments   []*ToDoComment
	minWords   int
	minChars   int
	addedMap   map[string]bool
	commentMux sync.Mutex
}
func NewToDoGenerator(root string, include []string, exclude []string, minWords, minChars int) *ToDoGenerator {
	log.Printf("Using source code root %v", root)
	log.Printf("Using %v include filters", include)
	ifilters := make([]*regexp.Regexp, 0, len(include))
	for _, f := range include {
		ifilters = append(ifilters, regexp.MustCompile(f))
	}
	log.Printf("Using %v exclude filters", exclude)
	efilters := make([]*regexp.Regexp, 0, len(exclude))
	for _, f := range exclude {
		efilters = append(efilters, regexp.MustCompile(f))
	}
	absolutePath, err := filepath.Abs(root)
	if err != nil {
		log.Printf("Error setting generator root: %v", err)
		absolutePath = root
	}
	return &ToDoGenerator{
		root:     absolutePath,
		include:  ifilters,
		exclude:  efilters,
		minWords: minWords,
		minChars: minChars,
		comments: make([]*ToDoComment, 0),
		addedMap: make(map[string]bool),
	}
}
func (td *ToDoGenerator) Root() string {
	return td.root
}
func (td *ToDoGenerator) Includes(path string) bool {
	anyMatch := false
	for _, f := range td.include {
		if f.MatchString(path) {
			anyMatch = true
			break
		}
	}
	if !anyMatch && len(td.include) > 0 {
		return false
	}
	return true
}
func (td *ToDoGenerator) Excludes(path string) bool {
	anyMatch := false
	for _, f := range td.exclude {
		if f.MatchString(path) {
			anyMatch = true
			break
		}
	}
	return anyMatch
}