func ParseTemplateFileName(fileName string, opts config.FileOpts) (t time.Time, ok bool) {
	ext := filepath.Ext(fileName)
	if ext == "." {
		return t, false
	}
	if strings.TrimPrefix(ext, ".") != opts.Ext {
		return t, false
	}
	baseName := strings.TrimSuffix(fileName, ext)
	t, err := time.Parse(opts.TimeFormat, baseName)
	if err != nil {
		return t, false
	}
	return t, true
}
ParseTemplateFileName extracts a time.Time from a file name and returns an additional
bool indicating if name corresponds to a valid template file name
package template
import (
	"fmt"
	"io"
	"path/filepath"
	"strings"
	"time"
	"github.com/dkaslovsky/textnote/pkg/config"
	"github.com/pkg/errors"
)
type Template struct {
	opts       config.Opts
	date       time.Time
	sections   []*section
	sectionIdx map[string]int  
}
func NewTemplate(opts config.Opts, date time.Time) *Template {
	t := &Template{
		opts:       opts,
		date:       date,
		sections:   []*section{},
		sectionIdx: map[string]int{},
	}
	for idx, sectionName := range opts.Section.Names {
		t.sections = append(t.sections, newSection(sectionName))
		t.sectionIdx[sectionName] = idx
	}
	return t
}
func (t *Template) Write(w io.Writer) error {
	_, err := w.Write([]byte(t.string()))
	return err
}
func (t *Template) GetDate() time.Time {
	return t.date
}
func (t *Template) GetFileCursorLine() int {
	return t.opts.File.CursorLine
}
func (t *Template) GetFilePath() string {
	name := filepath.Join(t.opts.AppDir, t.date.Format(t.opts.File.TimeFormat))
	if t.opts.File.Ext == "" {
		return name
	}
	return fmt.Sprintf("%s.%s", name, t.opts.File.Ext)
}
type sectionGettable interface {
	getSection(string) (*section, error)
}
func (t *Template) CopySectionContents(src sectionGettable, sectionName string) error {
	tgtSec, err := t.getSection(sectionName)
	if err != nil {
		return errors.Wrap(err, "failed to find section in target")
	}
	srcSec, err := src.getSection(sectionName)
	if err != nil {
		return errors.Wrap(err, "failed to find section in source")
	}
	tgtSec.contents = append(tgtSec.contents, srcSec.contents...)
	return nil
}
func (t *Template) DeleteSectionContents(sectionName string) error {
	sec, err := t.getSection(sectionName)
	if err != nil {
		return errors.Wrap(err, "cannot delete section")
	}
	sec.deleteContents()
	return nil
}
func (t *Template) IsEmpty() bool {
	for _, sec := range t.sections {
		if !sec.isEmpty() {
			return false
		}
	}
	return true
}
func (t *Template) Load(r io.Reader) error {
	raw, err := io.ReadAll(r)
	if err != nil {
		return errors.Wrap(err, "error loading template")
	}
	sectionText := string(raw)
	sectionNameRegex, err := getSectionNameRegex(t.opts.Section.Prefix, t.opts.Section.Suffix)
	if err != nil {
		return errors.Wrap(err, "cannot parse sections")
	}
	sectionBoundaries := sectionNameRegex.FindAllStringSubmatchIndex(sectionText, -1)
	numSections := len(sectionBoundaries)
	for i, idxs := range sectionBoundaries {
		var curSecEnd int
		if i != numSections-1 {
			curSecEnd = sectionBoundaries[i+1][0]
		} else {
			curSecEnd = len(sectionText)
		}
		section, err := parseSection(sectionText[idxs[0]:curSecEnd], t.opts)
		if err != nil {
			return errors.Wrap(err, "failed to parse section while reading textnote")
		}
		idx, found := t.sectionIdx[section.name]
		if !found {
			return fmt.Errorf("cannot load undefined section [%s]", section.name)
		}
		t.sections[idx] = section
	}
	return nil
}
func (t *Template) string() string {
	str := t.makeHeader()
	for _, section := range t.sections {
		name := section.getNameString(t.opts.Section.Prefix, t.opts.Section.Suffix)
		body := section.getContentString()
		if len(body) == 0 {
			body = strings.Repeat("\n", t.opts.Section.TrailingNewlines)
		}
		str += fmt.Sprintf("%s%s", name, body)
	}
	return str
}
func (t *Template) makeHeader() string {
	return fmt.Sprintf("%s%s%s\n%s",
		t.opts.Header.Prefix,
		t.date.Format(t.opts.Header.TimeFormat),
		t.opts.Header.Suffix,
		strings.Repeat("\n", t.opts.Header.TrailingNewlines),
	)
}
func (t *Template) getSection(name string) (*section, error) {
	idx, found := t.sectionIdx[name]
	if !found {
		return &section{}, fmt.Errorf("section [%s] not found", name)
	}
	return t.sections[idx], nil
}