package file import ( "bufio" "bytes" "errors" "io" "io/fs" "os" "path/filepath" "strings" "time" "git.andreafazzi.eu/andrea/probo/models" "git.andreafazzi.eu/andrea/probo/store" "gopkg.in/yaml.v2" ) type QuizFileStore = FileStore[*models.Quiz, *store.QuizStore] func NewQuizFileStore(config *FileStoreConfig[*models.Quiz, *store.QuizStore]) (*QuizFileStore, error) { return NewFileStore[*models.Quiz, *store.QuizStore](config, store.NewQuizStore()) } func NewDefaultQuizFileStore() (*QuizFileStore, error) { return NewQuizFileStore( &FileStoreConfig[*models.Quiz, *store.QuizStore]{ FilePathConfig: FilePathConfig{GetDefaultQuizzesDir(), "quiz", ".md"}, IndexDirFunc: DefaultQuizIndexDirFunc, }, ) } func DefaultQuizIndexDirFunc(s *QuizFileStore) error { files, err := os.ReadDir(s.Dir) if err != nil { return err } entityFiles := make([]fs.DirEntry, 0) for _, file := range files { filename := file.Name() if !file.IsDir() && strings.HasSuffix(filename, s.FileSuffix) { entityFiles = append(entityFiles, file) } } for _, file := range entityFiles { filename := file.Name() fullPath := filepath.Join(s.Dir, filename) content, err := os.ReadFile(fullPath) if err != nil { return err } var entity = new(models.Quiz) err = entity.Unmarshal(content) if err != nil { return err } var errQuizAlreadyPresent *store.ErrQuizAlreadyPresent mEntity, err := s.Storer.Create(entity) if err != nil && !errors.As(err, &errQuizAlreadyPresent) { return err } if entity.ID == "" { writeQuizHeader(fullPath, &models.Meta{ ID: mEntity.ID, CreatedAt: time.Now(), }) } s.SetPath(mEntity, fullPath) } return nil } func writeQuizHeader(path string, meta *models.Meta) (*models.Meta, error) { readMeta, err := readQuizHeader(path) if err != nil { return nil, err } if readMeta == nil { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() var buffer bytes.Buffer header, err := yaml.Marshal(meta) if err != nil { return nil, err } _, err = buffer.WriteString("---\n" + string(header) + "---\n") if err != nil { return nil, err } _, err = io.Copy(&buffer, file) if err != nil { return nil, err } file, err = os.Create(path) if err != nil { return nil, err } defer file.Close() _, err = io.Copy(file, &buffer) if err != nil { return nil, err } } return meta, nil } func readQuizHeader(path string) (*models.Meta, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } meta, _, err := models.ParseMetaHeaderFromMarkdown(string(data)) if err != nil { return nil, err } return meta, nil } func addMetaHeaderToMarkdown(content string, meta *models.Meta) (string, error) { var buffer bytes.Buffer header, err := yaml.Marshal(meta) if err != nil { return "", err } _, err = buffer.WriteString("---\n" + string(header) + "---\n") if err != nil { return "", err } _, err = buffer.WriteString(content) if err != nil { return "", err } return buffer.String(), nil } func removeQuizHeader(path string) (*models.Meta, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() var buffer bytes.Buffer reader := bufio.NewReader(file) var meta models.Meta var line string var sb strings.Builder for { line, err = reader.ReadString('\n') if err != nil { if err == io.EOF { break } return nil, err } if strings.TrimSpace(line) == "---" { break } } for { line, err = reader.ReadString('\n') if err != nil { if err == io.EOF { break } return nil, err } if strings.TrimSpace(line) == "---" { break } sb.WriteString(line) } err = yaml.Unmarshal([]byte(sb.String()), &meta) if err != nil { return nil, err } _, err = io.Copy(&buffer, reader) if err != nil { return nil, err } file, err = os.Create(path) if err != nil { return nil, err } defer file.Close() _, err = io.Copy(file, &buffer) if err != nil { return nil, err } return &meta, nil }