diff --git a/README.md b/README.md index c435bac..58e1ef6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Probo Collector -A backend to collect quizzes in a RESTful way. +A backend to collect quizzes in a RESTful way. \ No newline at end of file diff --git a/cmd/filter.go b/cmd/filter.go index 1508240..b6a42d2 100644 --- a/cmd/filter.go +++ b/cmd/filter.go @@ -5,13 +5,11 @@ package cmd import ( "fmt" - "log" "os" "git.andreafazzi.eu/andrea/probo/cmd/filter" "git.andreafazzi.eu/andrea/probo/cmd/util" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/huh" "github.com/charmbracelet/lipgloss" "github.com/muesli/termenv" "github.com/spf13/cobra" @@ -37,32 +35,15 @@ func runFilter(cmd *cobra.Command, args []string) { panic(err) } - if len(args) < 1 { - f := util.LogToFile() - if f != nil { - defer f.Close() - } - - lipgloss.SetColorProfile(termenv.TrueColor) - - form := huh.NewForm(huh.NewGroup( - huh.NewSelect[string](). - Title("Choose the store you want to filter"). - Options( - huh.NewOption("Participants", "participants"), - huh.NewOption("Quizzes", "quizzes"), - huh.NewOption("Responses", "responses"), - ).Value(&storeType), - )) - - err = form.Run() - if err != nil { - log.Fatal(err) - } - } else { - storeType = args[0] + f := util.LogToFile() + if f != nil { + defer f.Close() } + lipgloss.SetColorProfile(termenv.TrueColor) + + storeType = args[0] + model, err := tea.NewProgram( filter.New(path, storeType, util.ReadStdin()), tea.WithOutput(os.Stderr), @@ -77,5 +58,4 @@ func runFilter(cmd *cobra.Command, args []string) { if result.Result != "" { fmt.Fprintf(os.Stdout, result.Result) } - } diff --git a/cmd/rank.go b/cmd/rank.go new file mode 100644 index 0000000..b3a04ba --- /dev/null +++ b/cmd/rank.go @@ -0,0 +1,59 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "fmt" + "os" + + "git.andreafazzi.eu/andrea/probo/cmd/rank" + "git.andreafazzi.eu/andrea/probo/cmd/util" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/muesli/termenv" + "github.com/spf13/cobra" +) + +// rankCmd represents the rank command +var rankCmd = &cobra.Command{ + Use: "rank", + Short: "Show a ranking from the given responses.", + Long: util.RenderMarkdownTemplates("cli/*.tmpl", "cli/rank/*.tmpl"), + Run: runRank, +} + +func runRank(cmd *cobra.Command, args []string) { + path, err := cmd.Flags().GetString("input") + if err != nil { + panic(err) + } + + f := util.LogToFile() + if f != nil { + defer f.Close() + } + + lipgloss.SetColorProfile(termenv.TrueColor) + + model, err := tea.NewProgram( + rank.New(path, util.ReadStdin()), + tea.WithOutput(os.Stderr), + ).Run() + if err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } + + result := model.(*rank.RankModel) + + if result.Result != "" { + fmt.Fprintf(os.Stdout, result.Result) + } + +} + +func init() { + rootCmd.AddCommand(rankCmd) + rootCmd.PersistentFlags().StringP("input", "i", "", "Specify an input file") +} diff --git a/cmd/rank/format.go b/cmd/rank/format.go new file mode 100644 index 0000000..5bc01df --- /dev/null +++ b/cmd/rank/format.go @@ -0,0 +1,9 @@ +package rank + +var ( + stateFormats = map[int][]string{ + BrowseState: []string{"BROWSE 📖", "Total scores: %d", "🟢"}, + ExecutingScriptState: []string{"EXE %s", "Executing script...", "🔴"}, + ErrorState: []string{"ERROR 📖", "%v", "🔴"}, + } +) diff --git a/cmd/rank/keymap.go b/cmd/rank/keymap.go new file mode 100644 index 0000000..eb377d5 --- /dev/null +++ b/cmd/rank/keymap.go @@ -0,0 +1,62 @@ +package rank + +import ( + "github.com/charmbracelet/bubbles/key" + "github.com/remogatto/sugarfoam/components/group" + "github.com/remogatto/sugarfoam/components/table" + "github.com/remogatto/sugarfoam/components/viewport" +) + +type keyBindings struct { + group *group.Model + + quit, enter key.Binding +} + +func (k *keyBindings) ShortHelp() []key.Binding { + keys := make([]key.Binding, 0) + + current := k.group.Current() + + switch item := current.(type) { + case *table.Model: + keys = append( + keys, + item.KeyMap.LineUp, + item.KeyMap.LineDown, + ) + + case *viewport.Model: + keys = append( + keys, + item.KeyMap.Up, + item.KeyMap.Down, + ) + } + + keys = append( + keys, + k.group.KeyMap.FocusNext, + k.group.KeyMap.FocusPrev, + k.quit, + ) + + return keys +} + +func (k keyBindings) FullHelp() [][]key.Binding { + return [][]key.Binding{ + { + k.quit, + }, + } +} + +func newBindings(g *group.Model) *keyBindings { + return &keyBindings{ + group: g, + quit: key.NewBinding( + key.WithKeys("esc"), key.WithHelp("esc", "quit app"), + ), + } +} diff --git a/cmd/rank/message.go b/cmd/rank/message.go new file mode 100644 index 0000000..9d59bc6 --- /dev/null +++ b/cmd/rank/message.go @@ -0,0 +1,19 @@ +package rank + +import "git.andreafazzi.eu/andrea/probo/pkg/store/file" + +type storeLoadedMsg struct { + store *file.SessionFileStore +} + +type resultMsg struct { + result []any +} + +type errorMsg struct { + error error +} + +type scriptExecutedMsg struct { + result string +} diff --git a/cmd/rank/rank.go b/cmd/rank/rank.go new file mode 100644 index 0000000..961e443 --- /dev/null +++ b/cmd/rank/rank.go @@ -0,0 +1,448 @@ +package rank + +import ( + "bytes" + "cmp" + "encoding/json" + "errors" + "fmt" + "os" + "slices" + "strconv" + "text/template" + + "git.andreafazzi.eu/andrea/probo/pkg/models" + "git.andreafazzi.eu/andrea/probo/pkg/store/file" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" + btTable "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + "github.com/d5/tengo/v2" + "github.com/d5/tengo/v2/stdlib" + foam "github.com/remogatto/sugarfoam" + "github.com/remogatto/sugarfoam/components/group" + "github.com/remogatto/sugarfoam/components/header" + "github.com/remogatto/sugarfoam/components/help" + "github.com/remogatto/sugarfoam/components/statusbar" + "github.com/remogatto/sugarfoam/components/table" + "github.com/remogatto/sugarfoam/components/viewport" + "github.com/remogatto/sugarfoam/layout" + "github.com/remogatto/sugarfoam/layout/tiled" +) + +var responseTmpl = `{{range $answer := .Answers}} +{{$answer.Quiz|toMarkdown $.Width}} + R: {{$answer|toLipgloss}} +{{end}} +` + +type ParticipantScore struct { + Participant *models.Participant `json:"participant"` + Response *models.Response `json:"response"` + Score int `json:"score"` +} + +type Rank struct { + Scores []*ParticipantScore `json:"scores"` +} + +type RankModel struct { + // UI + viewport *viewport.Model + table *table.Model + group *group.Model + help *help.Model + statusBar *statusbar.Model + spinner spinner.Model + + // Layout + document *layout.Layout + + // Key bindings + bindings *keyBindings + + // json + InputJson string + Result string + + // session + rank *Rank + + // response + responseTmpl *template.Template + + // filter file + scriptFilePath string + + // markdown + mdRenderer *glamour.TermRenderer + + state int +} + +func New(path string, stdin string) *RankModel { + viewport := viewport.New() + + table := table.New(table.WithRelWidths(10, 20, 30, 30, 10)) + table.Model.SetColumns([]btTable.Column{ + {Title: "Pos", Width: 5}, + {Title: "Token", Width: 10}, + {Title: "Lastname", Width: 40}, + {Title: "Firstname", Width: 40}, + {Title: "Score", Width: 5}, + }) + + group := group.New( + group.WithItems(table, viewport), + group.WithLayout( + layout.New( + layout.WithStyles(&layout.Styles{Container: lipgloss.NewStyle().Padding(1, 1)}), + layout.WithItem(tiled.New(table, viewport)), + ), + ), + ) + + bindings := newBindings(group) + statusBar := statusbar.New(bindings) + + s := spinner.New( + spinner.WithStyle( + lipgloss.NewStyle().Foreground(lipgloss.Color("265"))), + ) + s.Spinner = spinner.Dot + + header := header.New( + header.WithContent( + lipgloss.NewStyle(). + Bold(true). + Border(lipgloss.NormalBorder(), false, false, true, false). + Render("😎 Rank 😎"), + ), + ) + + help := help.New( + bindings, + help.WithStyles(&foam.Styles{NoBorder: lipgloss.NewStyle().Padding(1, 1)})) + + document := layout.New( + layout.WithStyles(&layout.Styles{Container: lipgloss.NewStyle().Margin(1)}), + layout.WithItem(header), + layout.WithItem(group), + layout.WithItem(help), + layout.WithItem(statusBar), + ) + + renderer, err := glamour.NewTermRenderer( + glamour.WithStandardStyle("dracula"), + glamour.WithWordWrap(80), + ) + if err != nil { + panic(err) + } + + tmpl, err := template.New("response"). + Funcs(template.FuncMap{ + "toMarkdown": func(width int, quiz *models.Quiz) string { + md, err := models.QuizToMarkdown(quiz) + if err != nil { + panic(err) + } + + result, err := renderer.Render(md) + if err != nil { + panic(err) + } + + return result + }, + "toLipgloss": func(answer *models.ParticipantAnswer) string { + color := "#ff0000" + if answer.Correct { + color = "#00ff00" + } + return lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render(answer.Answer.Text) + }, + }). + Parse(responseTmpl) + if err != nil { + panic(err) + } + + return &RankModel{ + table: table, + viewport: viewport, + group: group, + statusBar: statusBar, + spinner: s, + document: document, + responseTmpl: tmpl, + bindings: bindings, + help: help, + scriptFilePath: path, + InputJson: stdin, + mdRenderer: renderer, + } + +} + +func (m *RankModel) Init() tea.Cmd { + var cmds []tea.Cmd + + cmds = append(cmds, m.group.Init(), m.executeScript(), m.spinner.Tick) + + m.group.Focus() + + return tea.Batch(cmds...) +} + +func (m *RankModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + + switch msg := msg.(type) { + + case tea.WindowSizeMsg: + m.handleWindowSize(msg) + + case tea.KeyMsg: + switch { + case key.Matches(msg, m.bindings.quit): + cmds = append(cmds, tea.Quit) + } + + case scriptExecutedMsg: + m.handleScriptExecuted(msg) + + case errorMsg: + m.handleError(msg) + m.state = ErrorState + } + + cmds = m.handleState(msg, cmds) + + return m, tea.Batch(cmds...) +} + +func (m *RankModel) View() string { + return m.document.View() +} + +func (m *RankModel) executeScript() tea.Cmd { + return func() tea.Msg { + if m.scriptFilePath == "" { + return nil + } + + rankJson, err := json.Marshal(Rank{Scores: make([]*ParticipantScore, 0)}) + if err != nil { + panic(err) + } + + script, err := os.ReadFile(m.scriptFilePath) + if err != nil { + panic(err) + } + + s := tengo.NewScript(script) + + s.SetImports(stdlib.GetModuleMap("fmt", "json", "rand", "times")) + _ = s.Add("input", m.InputJson) + _ = s.Add("output", string(rankJson)) + + c, err := s.Compile() + if err != nil { + panic(err) + } + + if err := c.Run(); err != nil { + panic(err) + } + + return scriptExecutedMsg{fmt.Sprintf("%s", c.Get("output"))} + + } +} + +func (m *RankModel) showErrorOnStatusBar(err error) { + m.statusBar.SetContent( + stateFormats[ErrorState][0], + fmt.Sprintf(stateFormats[ErrorState][1], err), + stateFormats[ErrorState][2], + ) +} + +func (m *RankModel) updateTableContent() { + rows := make([]btTable.Row, 0) + + for i, score := range m.rank.Scores { + rows = append(rows, btTable.Row{ + strconv.Itoa(i + 1), + score.Participant.Token, + score.Participant.Lastname, + score.Participant.Firstname, + strconv.Itoa(score.Score), + }) + + } + + m.table.SetRows(rows) +} + +func (m *RankModel) updateViewportContent() { + if len(m.table.Rows()) == 0 { + panic(errors.New("No scores available!")) + } + + currentPos := m.table.SelectedRow()[0] + + pos, err := strconv.Atoi(currentPos) + if err != nil { + panic(err) + } + + currentResponse := m.rank.Scores[pos-1] + + var buf bytes.Buffer + + var response struct { + *models.Response + Width int + } = struct { + *models.Response + Width int + }{currentResponse.Response, m.viewport.GetWidth()} + + err = m.responseTmpl.Execute(&buf, response) + if err != nil { + panic(err) + } + + m.viewport.SetContent(buf.String()) +} + +func (m *RankModel) handleWindowSize(msg tea.WindowSizeMsg) { + m.group.SetSize(msg.Width, msg.Height) + m.document.SetSize(msg.Width, msg.Height) + m.mdRenderer = m.createMDRenderer() +} + +func (m *RankModel) handleError(msg tea.Msg) { + err := msg.(errorMsg) + + m.statusBar.SetContent( + stateFormats[ErrorState][0], + fmt.Sprintf(stateFormats[ErrorState][1], err.error), + stateFormats[ErrorState][2], + ) +} + +func (m *RankModel) handleScriptExecuted(msg tea.Msg) { + rank := new(Rank) + jsonData := []byte(msg.(scriptExecutedMsg).result) + + err := json.Unmarshal(jsonData, &rank) + if err != nil { + panic(err) + } + + slices.SortFunc(rank.Scores, + func(a, b *ParticipantScore) int { + return cmp.Compare(b.Score, a.Score) + }) + + m.rank = rank + + m.updateTableContent() + m.updateViewportContent() + + m.state = BrowseState +} + +func (m *RankModel) handleState(msg tea.Msg, cmds []tea.Cmd) []tea.Cmd { + _, cmd := m.group.Update(msg) + + if m.state == ExecutingScriptState { + return m.updateSpinner(msg, cmd, cmds) + } + + if m.state == BrowseState { + m.updateViewportContent() + } + + if m.state != ErrorState { + m.statusBar.SetContent( + stateFormats[BrowseState][0], + fmt.Sprintf(stateFormats[BrowseState][1], len(m.rank.Scores)), + stateFormats[BrowseState][2], + ) + } + + cmds = append(cmds, cmd) + + return cmds +} + +func (m *RankModel) updateSpinner(msg tea.Msg, cmd tea.Cmd, cmds []tea.Cmd) []tea.Cmd { + m.spinner, cmd = m.spinner.Update(msg) + + m.statusBar.SetContent(fmt.Sprintf(stateFormats[m.state][0], m.spinner.View()), stateFormats[m.state][1], stateFormats[m.state][2]) + + cmds = append(cmds, cmd) + + return cmds +} + +func (m *RankModel) loadStore() tea.Cmd { + return func() tea.Msg { + sStore, err := file.NewDefaultSessionFileStore() + if err != nil { + panic(err) + } + + return storeLoadedMsg{sStore} + } +} + +func (m *RankModel) createMDRenderer() *glamour.TermRenderer { + renderer, err := glamour.NewTermRenderer( + glamour.WithStandardStyle("dracula"), + glamour.WithWordWrap(m.viewport.GetWidth()), + ) + if err != nil { + panic(err) + } + return renderer +} + +// func toColoredJson(data []any) (string, error) { +// result, err := json.MarshalIndent(data, "", " ") +// if err != nil { +// return "", err +// } + +// coloredBytes := make([]byte, 0) +// buffer := bytes.NewBuffer(coloredBytes) + +// err = quick.Highlight(buffer, string(result), "json", "terminal16m", "dracula") +// if err != nil { +// panic(err) +// } + +// return sanitize(buffer.String()), nil +// } + +// func sanitize(text string) string { +// // FIXME: The use of a standard '-' character causes rendering +// // issues within the viewport. Further investigation is +// // required to resolve this problem. +// return strings.Replace(text, "-", "–", -1) +// } + +// func desanitize(text string) string { +// // FIXME: The use of a standard '-' character causes rendering +// // issues within the viewport. Further investigation is +// // required to resolve this problem. +// return strings.Replace(text, "–", "-", -1) +// } diff --git a/cmd/rank/rank.go~ b/cmd/rank/rank.go~ new file mode 100644 index 0000000..166086d --- /dev/null +++ b/cmd/rank/rank.go~ @@ -0,0 +1,436 @@ +package rank + +import ( + "bytes" + "cmp" + "encoding/json" + "errors" + "fmt" + "os" + "slices" + "strconv" + "strings" + "text/template" + + "git.andreafazzi.eu/andrea/probo/pkg/models" + "git.andreafazzi.eu/andrea/probo/pkg/store/file" + "github.com/alecthomas/chroma/quick" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/spinner" + btTable "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/lipgloss" + "github.com/d5/tengo/v2" + "github.com/d5/tengo/v2/stdlib" + foam "github.com/remogatto/sugarfoam" + "github.com/remogatto/sugarfoam/components/group" + "github.com/remogatto/sugarfoam/components/header" + "github.com/remogatto/sugarfoam/components/help" + "github.com/remogatto/sugarfoam/components/statusbar" + "github.com/remogatto/sugarfoam/components/table" + "github.com/remogatto/sugarfoam/components/viewport" + "github.com/remogatto/sugarfoam/layout" + "github.com/remogatto/sugarfoam/layout/tiled" +) + +var responseTmpl = ` +{{range $answer := .Answers}} +{{$answer.Quiz|toMarkdown $.Width}} + R: {{$answer|toLipgloss}} +{{end}} +` + +type ParticipantScore struct { + Participant *models.Participant `json:"participant"` + Response *models.Response `json:"response"` + Score int `json:"score"` +} + +type Rank struct { + Scores []*ParticipantScore `json:"scores"` +} + +type RankModel struct { + // UI + viewport *viewport.Model + table *table.Model + group *group.Model + help *help.Model + statusBar *statusbar.Model + spinner spinner.Model + + // Layout + document *layout.Layout + + // Key bindings + bindings *keyBindings + + // json + InputJson string + Result string + + // session + rank *Rank + + // response + responseTmpl *template.Template + + // filter file + scriptFilePath string + + state int +} + +func New(path string, stdin string) *RankModel { + viewport := viewport.New() + + table := table.New(table.WithRelWidths(10, 20, 30, 30, 10)) + table.Model.SetColumns([]btTable.Column{ + {Title: "Pos", Width: 5}, + {Title: "Token", Width: 10}, + {Title: "Lastname", Width: 40}, + {Title: "Firstname", Width: 40}, + {Title: "Score", Width: 5}, + }) + + group := group.New( + group.WithItems(table, viewport), + group.WithLayout( + layout.New( + layout.WithStyles(&layout.Styles{Container: lipgloss.NewStyle().Padding(1, 1)}), + layout.WithItem(tiled.New(table, viewport)), + ), + ), + ) + + bindings := newBindings(group) + statusBar := statusbar.New(bindings) + + s := spinner.New( + spinner.WithStyle( + lipgloss.NewStyle().Foreground(lipgloss.Color("265"))), + ) + s.Spinner = spinner.Dot + + header := header.New( + header.WithContent( + lipgloss.NewStyle(). + Bold(true). + Border(lipgloss.NormalBorder(), false, false, true, false). + Render("😎 Rank 😎"), + ), + ) + + help := help.New( + bindings, + help.WithStyles(&foam.Styles{NoBorder: lipgloss.NewStyle().Padding(1, 1)})) + + document := layout.New( + layout.WithStyles(&layout.Styles{Container: lipgloss.NewStyle().Margin(1)}), + layout.WithItem(header), + layout.WithItem(group), + layout.WithItem(help), + layout.WithItem(statusBar), + ) + + tmpl, err := template.New("response"). + Funcs(template.FuncMap{ + "toMarkdown": func(width int, quiz *models.Quiz) string { + md, err := models.QuizToMarkdown(quiz) + if err != nil { + panic(err) + } + + renderer, err := glamour.NewTermRenderer( + glamour.WithStandardStyle("dracula"), + glamour.WithWordWrap(width), + ) + if err != nil { + panic(err) + } + + result, err := renderer.Render(md) + if err != nil { + panic(err) + } + + return result + }, + "toLipgloss": func(answer *models.ParticipantAnswer) string { + color := "#ff0000" + if answer.Correct { + color = "#00ff00" + } + return lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color(color)).Render(answer.Answer.Text) + }, + }). + Parse(responseTmpl) + if err != nil { + panic(err) + } + + return &RankModel{ + table: table, + viewport: viewport, + group: group, + statusBar: statusBar, + spinner: s, + document: document, + responseTmpl: tmpl, + bindings: bindings, + help: help, + scriptFilePath: path, + InputJson: stdin, + } + +} + +func (m *RankModel) Init() tea.Cmd { + var cmds []tea.Cmd + + cmds = append(cmds, m.group.Init(), m.executeScript(), m.spinner.Tick) + + m.group.Focus() + + return tea.Batch(cmds...) +} + +func (m *RankModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + + switch msg := msg.(type) { + + case tea.WindowSizeMsg: + m.handleWindowSize(msg) + + case tea.KeyMsg: + switch { + case key.Matches(msg, m.bindings.quit): + cmds = append(cmds, tea.Quit) + } + + case scriptExecutedMsg: + m.handleScriptExecuted(msg) + + case errorMsg: + m.handleError(msg) + m.state = ErrorState + } + + cmds = m.handleState(msg, cmds) + + return m, tea.Batch(cmds...) +} + +func (m *RankModel) View() string { + return m.document.View() +} + +func (m *RankModel) executeScript() tea.Cmd { + return func() tea.Msg { + if m.scriptFilePath == "" { + return nil + } + + rankJson, err := json.Marshal(Rank{Scores: make([]*ParticipantScore, 0)}) + if err != nil { + panic(err) + } + + script, err := os.ReadFile(m.scriptFilePath) + if err != nil { + panic(err) + } + + s := tengo.NewScript(script) + + s.SetImports(stdlib.GetModuleMap("fmt", "json", "rand", "times")) + _ = s.Add("input", m.InputJson) + _ = s.Add("output", string(rankJson)) + + c, err := s.Compile() + if err != nil { + panic(err) + } + + if err := c.Run(); err != nil { + panic(err) + } + + return scriptExecutedMsg{fmt.Sprintf("%s", c.Get("output"))} + + } +} + +func (m *RankModel) showErrorOnStatusBar(err error) { + m.statusBar.SetContent( + stateFormats[ErrorState][0], + fmt.Sprintf(stateFormats[ErrorState][1], err), + stateFormats[ErrorState][2], + ) +} + +func (m *RankModel) updateTableContent() { + rows := make([]btTable.Row, 0) + + for i, score := range m.rank.Scores { + rows = append(rows, btTable.Row{ + strconv.Itoa(i + 1), + score.Participant.Token, + score.Participant.Lastname, + score.Participant.Firstname, + strconv.Itoa(score.Score), + }) + + } + + m.table.SetRows(rows) +} + +func (m *RankModel) updateViewportContent() { + if len(m.table.Rows()) == 0 { + panic(errors.New("No scores available!")) + } + + currentPos := m.table.SelectedRow()[0] + + pos, err := strconv.Atoi(currentPos) + if err != nil { + panic(err) + } + + currentResponse := m.rank.Scores[pos-1] + + var buf bytes.Buffer + + var response struct { + *models.Response + Width int + } = struct { + *models.Response + Width int + }{currentResponse.Response, m.viewport.GetWidth()} + + err = m.responseTmpl.Execute(&buf, response) + if err != nil { + panic(err) + } + + m.viewport.SetContent(sanitize(buf.String())) + +} + +func (m *RankModel) handleWindowSize(msg tea.WindowSizeMsg) { + m.group.SetSize(msg.Width, msg.Height) + m.document.SetSize(msg.Width, msg.Height) +} + +func (m *RankModel) handleError(msg tea.Msg) { + err := msg.(errorMsg) + + m.statusBar.SetContent( + stateFormats[ErrorState][0], + fmt.Sprintf(stateFormats[ErrorState][1], err.error), + stateFormats[ErrorState][2], + ) +} + +func (m *RankModel) handleScriptExecuted(msg tea.Msg) { + rank := new(Rank) + jsonData := []byte(msg.(scriptExecutedMsg).result) + + err := json.Unmarshal(jsonData, &rank) + if err != nil { + panic(err) + } + + slices.SortFunc(rank.Scores, + func(a, b *ParticipantScore) int { + return cmp.Compare(b.Score, a.Score) + }) + + m.rank = rank + + m.updateTableContent() + m.updateViewportContent() + + m.state = BrowseState +} + +func (m *RankModel) handleState(msg tea.Msg, cmds []tea.Cmd) []tea.Cmd { + _, cmd := m.group.Update(msg) + + if m.state == ExecutingScriptState { + return m.updateSpinner(msg, cmd, cmds) + } + + if m.state == BrowseState { + m.updateViewportContent() + } + + if m.state != ErrorState { + m.statusBar.SetContent( + stateFormats[BrowseState][0], + fmt.Sprintf(stateFormats[BrowseState][1], len(m.rank.Scores)), + stateFormats[BrowseState][2], + ) + } + + cmds = append(cmds, cmd) + + return cmds +} + +func (m *RankModel) updateSpinner(msg tea.Msg, cmd tea.Cmd, cmds []tea.Cmd) []tea.Cmd { + m.spinner, cmd = m.spinner.Update(msg) + + m.statusBar.SetContent(fmt.Sprintf(stateFormats[m.state][0], m.spinner.View()), stateFormats[m.state][1], stateFormats[m.state][2]) + + cmds = append(cmds, cmd) + + return cmds +} + +func (m *RankModel) loadStore() tea.Cmd { + return func() tea.Msg { + sStore, err := file.NewDefaultSessionFileStore() + if err != nil { + panic(err) + } + + return storeLoadedMsg{sStore} + } +} + +func toColoredJson(data []any) (string, error) { + result, err := json.MarshalIndent(data, "", " ") + if err != nil { + return "", err + } + + coloredBytes := make([]byte, 0) + buffer := bytes.NewBuffer(coloredBytes) + + err = quick.Highlight(buffer, string(result), "json", "terminal16m", "dracula") + if err != nil { + panic(err) + } + + return sanitize(buffer.String()), nil +} + +func sanitize(text string) string { + // FIXME: The use of a standard '-' character causes rendering + // issues within the viewport. Further investigation is + // required to resolve this problem. + return strings.Replace(text, "-", "–", -1) +} + +func desanitize(text string) string { + // FIXME: The use of a standard '-' character causes rendering + // issues within the viewport. Further investigation is + // required to resolve this problem. + return strings.Replace(text, "–", "-", -1) +} diff --git a/cmd/rank/state.go b/cmd/rank/state.go new file mode 100644 index 0000000..6990083 --- /dev/null +++ b/cmd/rank/state.go @@ -0,0 +1,7 @@ +package rank + +const ( + ExecutingScriptState = iota + BrowseState + ErrorState +) diff --git a/cmd/session/session.go b/cmd/session/session.go index 701c6ad..36867ab 100644 --- a/cmd/session/session.go +++ b/cmd/session/session.go @@ -1,16 +1,13 @@ package session import ( - "bytes" "encoding/json" "errors" "fmt" "os" - "strings" "git.andreafazzi.eu/andrea/probo/pkg/models" "git.andreafazzi.eu/andrea/probo/pkg/store/file" - "github.com/alecthomas/chroma/quick" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/spinner" @@ -33,6 +30,9 @@ import ( "github.com/remogatto/sugarfoam/layout/tiled" ) +var mockSession = ` +{"participants":[{"Attributes":{"class":"Math 101A"},"created_at":"2024-04-03T09:22:11.201142494+02:00","firstname":"THOMAS","id":"1024758d-0dba-4bec-aaab-339e87a0649b","lastname":"MANN","token":"427628","updated_at":"2024-05-27T13:48:50.096492274+02:00"},{"Attributes":{"class":"Math 101A"},"created_at":"2024-04-04T10:23:12.203143495+02:00","firstname":"JAMES","id":"f3c5a7bd-6fda-4bce-bbbb-333e88a0650c","lastname":"BROWN","token":"428629","updated_at":"2024-05-27T13:48:50.075089555+02:00"},{"Attributes":{"class":"Math 101A"},"created_at":"2024-04-05T11:24:13.204144496+02:00","firstname":"LUCY","id":"g4d6c8cd-7eda-4cce-cccc-444e89a0661d","lastname":"DOE","token":"429630","updated_at":"2024-05-27T13:48:50.079134348+02:00"}], "quizzes": [{"CorrectPos":0,"answers":[{"created_at":"2024-05-27T13:55:26.448679335+02:00","id":"6771592e-4e2c-45f1-b14f-91f943a5616b","text":"3 + 1 = 1 + 3","updated_at":"2024-05-27T13:55:26.448679428+02:00"},{"created_at":"2024-05-27T13:55:26.448681412+02:00","id":"bad7db2a-f5be-4f22-879a-fd783bb1c0a9","text":"3 - 1 = 1 - 3","updated_at":"2024-05-27T13:55:26.448681456+02:00"},{"created_at":"2024-05-27T13:55:26.448683251+02:00","id":"8ae29e51-65a7-4618-8074-58ee90c4fb83","text":"3 / 2 = 2 / 3","updated_at":"2024-05-27T13:55:26.448683289+02:00"},{"created_at":"2024-05-27T13:55:26.448685048+02:00","id":"7d2a9491-338d-4274-9e62-3d2641788777","text":"-3 + 1 = -1 + 3","updated_at":"2024-05-27T13:55:26.448685085+02:00"}],"correct":{"created_at":"2024-05-27T13:55:26.448679335+02:00","id":"6771592e-4e2c-45f1-b14f-91f943a5616b","text":"3 + 1 = 1 + 3","updated_at":"2024-05-27T13:55:26.448679428+02:00"},"created_at":"2024-05-27T13:49:24.170080837+02:00","hash":"","id":"6a21d3fa-d63c-4acf-9238-551692ed74c4","question":{"created_at":"2024-05-27T13:55:26.448673273+02:00","id":"bf19c1f1-6896-4ca2-b7dc-1657adcce4cd","text":"In #mathematics, for the commutative property of addition, we have\nthat:","updated_at":"2024-05-27T13:55:26.448673633+02:00"},"tags":["#mathematics"],"type":0,"updated_at":"2024-05-27T13:55:26.448701931+02:00"},{"CorrectPos":0,"answers":[{"created_at":"2024-05-27T13:55:26.449222989+02:00","id":"37f741c2-12de-4463-b6c0-42b8b5249e7a","text":"3 * (4 + 5) = 3 * 4 + 3 * 5","updated_at":"2024-05-27T13:55:26.449223079+02:00"},{"created_at":"2024-05-27T13:55:26.44922686+02:00","id":"c60b1b81-5e16-4401-831c-d9fffba1d1eb","text":"6 * (2 - 3) = 6 * 2 + 6 * 3","updated_at":"2024-05-27T13:55:26.449226924+02:00"},{"created_at":"2024-05-27T13:55:26.449230299+02:00","id":"fb0ba30c-1e4e-41a1-9630-7f18aa24a6db","text":"7 * (8 + 2) = 7 * 8 - 7 * 2","updated_at":"2024-05-27T13:55:26.449230351+02:00"},{"created_at":"2024-05-27T13:55:26.449233993+02:00","id":"0cfd6fbb-7aac-4d35-aa17-3e62f5297c68","text":"2 * (1 + 2) = (- 2) * 1 - 4","updated_at":"2024-05-27T13:55:26.449234055+02:00"}],"correct":{"created_at":"2024-05-27T13:55:26.449222989+02:00","id":"37f741c2-12de-4463-b6c0-42b8b5249e7a","text":"3 * (4 + 5) = 3 * 4 + 3 * 5","updated_at":"2024-05-27T13:55:26.449223079+02:00"},"created_at":"2024-05-27T13:49:24.173550821+02:00","hash":"","id":"40aa188a-46c6-4f26-9a1e-4d5a028ba367","question":{"created_at":"2024-05-27T13:55:26.449216807+02:00","id":"723acb0a-5c60-4062-8615-fd3494220b4d","text":"In #mathematics, the distributive property allows us to multiply a sum\nor difference by a single number. Which statement correctly applies\nthis property?","updated_at":"2024-05-27T13:55:26.449216918+02:00"},"tags":["#mathematics"],"type":0,"updated_at":"2024-05-27T13:55:26.449281612+02:00"},{"CorrectPos":0,"answers":[{"created_at":"2024-05-27T13:55:26.449440615+02:00","id":"11be2252-45f0-44c1-a9f0-19dcdcd45b68","text":"1","updated_at":"2024-05-27T13:55:26.449440713+02:00"},{"created_at":"2024-05-27T13:55:26.449452222+02:00","id":"971fbf3b-27b1-4b67-9636-ac681056ce62","text":"0","updated_at":"2024-05-27T13:55:26.449452291+02:00"},{"created_at":"2024-05-27T13:55:26.449455664+02:00","id":"d35104b1-37f9-4312-aa21-a470650cb4dc","text":"2","updated_at":"2024-05-27T13:55:26.44945573+02:00"},{"created_at":"2024-05-27T13:55:26.449459322+02:00","id":"220077d2-1994-40ed-8d28-f39d2ebb8dcf","text":"3","updated_at":"2024-05-27T13:55:26.449459397+02:00"}],"correct":{"created_at":"2024-05-27T13:55:26.449440615+02:00","id":"11be2252-45f0-44c1-a9f0-19dcdcd45b68","text":"1","updated_at":"2024-05-27T13:55:26.449440713+02:00"},"created_at":"2024-05-27T13:49:24.175865713+02:00","hash":"","id":"f9fab318-3373-4729-a80f-b681460824e3","question":{"created_at":"2024-05-27T13:55:26.449432971+02:00","id":"af7d973a-a83c-4a18-ac36-92ab6442ef07","text":"In #mathematics, certain numbers have special properties. Identify the\nidentity element for multiplication among the options given:","updated_at":"2024-05-27T13:55:26.449433136+02:00"},"tags":["#mathematics"],"type":0,"updated_at":"2024-05-27T13:55:26.449476666+02:00"}]}` + type SessionModel struct { // UI form *form.Model @@ -288,7 +288,7 @@ func (m *SessionModel) updateTableContent(session *models.Session) { for _, exam := range session.Exams { rows = append(rows, btTable.Row{ - sanitize(exam.Participant.ID), + exam.Participant.ID, exam.Participant.Token, exam.Participant.Lastname, exam.Participant.Firstname, @@ -306,24 +306,30 @@ func (m *SessionModel) updateViewportContent(session *models.Session) { } currentUUID := m.table.SelectedRow()[0] - currentExam := session.Exams[desanitize(currentUUID)] + currentExam := session.Exams[currentUUID] if currentExam == nil { panic("Current token is not associate to any exam!") } - md, err := currentExam.ToMarkdown() - if err != nil { - m.showErrorOnStatusBar(err) - } + // md, err := currentExam.ToMarkdown() + // if err != nil { + // m.showErrorOnStatusBar(err) + // } - result, err := m.mdRenderer.Render(md) - if err != nil { - m.showErrorOnStatusBar(err) - } + // data, err := currentExam.Marshal() + // if err != nil { + // panic(err) + // } - m.viewport.SetContent(sanitize(result)) + // result, err := m.mdRenderer.Render(md) + // if err != nil { + // m.showErrorOnStatusBar(err) + // } + // m.viewport.SetContent(result) + // m.viewport.SetContent(string(data)) + m.viewport.SetContent(mockSession) } func (m *SessionModel) createMDRenderer() *glamour.TermRenderer { @@ -444,34 +450,3 @@ func (m *SessionModel) loadStore() tea.Cmd { return storeLoadedMsg{sStore} } } - -func toColoredJson(data []any) (string, error) { - result, err := json.MarshalIndent(data, "", " ") - if err != nil { - return "", err - } - - coloredBytes := make([]byte, 0) - buffer := bytes.NewBuffer(coloredBytes) - - err = quick.Highlight(buffer, string(result), "json", "terminal16m", "dracula") - if err != nil { - panic(err) - } - - return sanitize(buffer.String()), nil -} - -func sanitize(text string) string { - // FIXME: The use of a standard '-' character causes rendering - // issues within the viewport. Further investigation is - // required to resolve this problem. - return strings.Replace(text, "-", "–", -1) -} - -func desanitize(text string) string { - // FIXME: The use of a standard '-' character causes rendering - // issues within the viewport. Further investigation is - // required to resolve this problem. - return strings.Replace(text, "–", "-", -1) -} diff --git a/embed/cli/rank/description.tmpl b/embed/cli/rank/description.tmpl new file mode 100644 index 0000000..41d5480 --- /dev/null +++ b/embed/cli/rank/description.tmpl @@ -0,0 +1,21 @@ +{{define "description"}} +# Rank + +**The `rank` command generates a ranking of results based on participant answers.** + +With the `rank` command, it is possible to generate a ranking of +results based on the answers provided by participants. The command +accepts on standard input either a JSON ready for display or the +result of a filter applied to the answers that needs to be processed +through a `tengo` script to conform to the JSON schema. + +## Example + +Filtra le risposte date dai partecipanti al test "Math Test" e produce +una classifica dei punteggi assegnando +1 punto per ogni risposta +esatta fornita. + +``` +probo filter responses -i data/filters/math_test.jq | probo rank -s data/scripts/score.tengo +``` +{{end}} \ No newline at end of file diff --git a/embed/templates/exam/layout-exam.html.tmpl b/embed/templates/exam/layout-exam.html.tmpl index 53819ed..08dd132 100644 --- a/embed/templates/exam/layout-exam.html.tmpl +++ b/embed/templates/exam/layout-exam.html.tmpl @@ -28,7 +28,7 @@ -
+ {{template "content" .}} diff --git a/go.mod b/go.mod index b8259d2..9cc793f 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/charmbracelet/huh v0.3.0 github.com/charmbracelet/lipgloss v0.10.0 github.com/charmbracelet/log v0.4.0 + github.com/charmbracelet/x/ansi v0.1.2 github.com/d5/tengo/v2 v2.17.0 github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/golang-jwt/jwt v3.2.2+incompatible @@ -44,7 +45,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/microcosm-cc/bluemonday v1.0.21 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect diff --git a/go.sum b/go.sum index 56839e1..51d4c21 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMt github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM= github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM= +github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY= +github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= @@ -83,6 +85,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/microcosm-cc/bluemonday v1.0.21 h1:dNH3e4PSyE4vNX+KlRGHT5KrSvjeUkoNPwEORjffHJg= github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= diff --git a/go.work.sum b/go.work.sum index 1da348c..36630e3 100644 --- a/go.work.sum +++ b/go.work.sum @@ -5,6 +5,9 @@ cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LR cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= @@ -58,28 +61,57 @@ go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0 go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.153.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/pkg/store/file/file.go b/pkg/store/file/file.go index 57e7ecc..2cd8461 100644 --- a/pkg/store/file/file.go +++ b/pkg/store/file/file.go @@ -97,7 +97,8 @@ func DefaultIndexDirFunc[T FileStorable, K store.Storer[T]](s *FileStore[T, K]) return fmt.Errorf("An error occurred unmarshalling %v: %v", filename, err) } - mEntity, err := s.Create(entity, fullPath) + // mEntity, err := s.Create(entity, fullPath) + mEntity, err := s.Storer.Create(entity) if err != nil { return err } diff --git a/pkg/store/file/participant.go b/pkg/store/file/participant.go index 67fd405..bcde877 100644 --- a/pkg/store/file/participant.go +++ b/pkg/store/file/participant.go @@ -14,7 +14,7 @@ func NewParticipantFileStore(config *FileStoreConfig[*models.Participant, *store pStore := new(ParticipantFileStore) - pStore.FileStore, err = NewFileStore[*models.Participant, *store.ParticipantStore](config, store.NewParticipantStore()) + pStore.FileStore, err = NewFileStore(config, store.NewParticipantStore()) if err != nil { return nil, err }