diff --git a/.gitignore b/.gitignore index 6ab38c8..51436ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ .log +data + diff --git a/backup/main.go b/backup/main.go deleted file mode 100644 index 135ebc3..0000000 --- a/backup/main.go +++ /dev/null @@ -1,290 +0,0 @@ -package main - -import ( - "encoding/json" - "io" - "log/slog" - "math/rand" - "net/http" - "os" - "path/filepath" - "strconv" - "strings" - "text/template" - "time" - - "git.andreafazzi.eu/andrea/probo/pkg/models" - "git.andreafazzi.eu/andrea/probo/pkg/store" - "git.andreafazzi.eu/andrea/probo/pkg/store/file" - "github.com/lmittmann/tint" -) - -var ( - DefaultAssetDir = "assets" - DefaultDataDir = "data" - DefaultSessionDir = "sessions" - DefaultResponseDir = "responses" - DefaultTemplateDir = "templates" - DefaultStaticDir = "static" -) - -type Config struct { - SessionDir string - ResponseDir string - TemplateDir string - StaticDir string -} - -type ExamTemplateData struct { - *models.Exam - - SessionID string -} - -type Server struct { - config *Config - mux *http.ServeMux - - sessionFileStore *file.SessionFileStore - responseFileStore *file.ResponseFileStore -} - -func GetDefaultTemplateDir() string { - return filepath.Join(DefaultAssetDir, DefaultTemplateDir) -} - -func GetDefaultStaticDir() string { - return filepath.Join(DefaultAssetDir, DefaultStaticDir) -} - -func GetDefaultSessionDir() string { - return filepath.Join(DefaultDataDir, DefaultSessionDir) -} - -func GetDefaultResponseDir() string { - return filepath.Join(DefaultDataDir, DefaultResponseDir) -} - -func NewServer(config *Config) (*Server, error) { - _, err := os.Stat(config.SessionDir) - if err != nil { - return nil, err - } - - _, err = os.Stat(config.TemplateDir) - if err != nil { - return nil, err - } - - _, err = os.Stat(config.StaticDir) - if err != nil { - return nil, err - } - - sStore, err := file.NewSessionFileStore( - &file.FileStoreConfig[*models.Session, *store.SessionStore]{ - FilePathConfig: file.FilePathConfig{Dir: config.SessionDir, FilePrefix: "session", FileSuffix: ".json"}, - IndexDirFunc: file.DefaultIndexDirFunc[*models.Session, *store.SessionStore], - CreateEntityFunc: func() *models.Session { - return &models.Session{} - }, - }, - ) - if err != nil { - return nil, err - } - - rStore, err := file.NewResponseFileStore( - &file.FileStoreConfig[*models.Response, *store.ResponseStore]{ - FilePathConfig: file.FilePathConfig{Dir: config.ResponseDir, FilePrefix: "response", FileSuffix: ".json"}, - IndexDirFunc: file.DefaultIndexDirFunc[*models.Response, *store.ResponseStore], - CreateEntityFunc: func() *models.Response { - return &models.Response{} - }, - }, - ) - if err != nil { - return nil, err - } - - rStore.FilePathConfig = file.FilePathConfig{ - Dir: config.ResponseDir, - FilePrefix: "response", - FileSuffix: ".json", - } - - s := &Server{ - config, - http.NewServeMux(), - sStore, - rStore, - } - - s.mux.Handle("/static/", http.StripPrefix("/static", http.FileServer(http.Dir(config.StaticDir)))) - s.mux.HandleFunc("/create", s.createExamSessionHandler) - s.mux.HandleFunc("/responses/", s.getResponsesHandler) - s.mux.HandleFunc("/", s.getExamHandler) - - return s, nil -} - -func NewDefaultServer() (*Server, error) { - return NewServer(&Config{ - SessionDir: GetDefaultSessionDir(), - ResponseDir: GetDefaultResponseDir(), - TemplateDir: GetDefaultTemplateDir(), - StaticDir: GetDefaultStaticDir(), - }) -} - -func (s *Server) getResponsesHandler(w http.ResponseWriter, r *http.Request) { - result := make([]*models.Response, 0) - - urlParts := strings.Split(r.URL.Path, "/") - - sessionID := urlParts[2] - - if r.Method == "GET" { - session, err := s.sessionFileStore.Read(sessionID) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - for _, exam := range session.Exams { - responses := s.responseFileStore.ReadAll() - for _, r := range responses { - if r.ID == exam.ID { - result = append(result, r) - } - } - } - err = json.NewEncoder(w).Encode(result) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } -} - -func (s *Server) createExamSessionHandler(w http.ResponseWriter, r *http.Request) { - session := new(models.Session) - - data, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - err = session.Unmarshal(data) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - memorySession, err := s.sessionFileStore.Create(session) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - err = json.NewEncoder(w).Encode(memorySession) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - slog.Info("Received a new session", "session", memorySession) - -} - -func (s *Server) getExamHandler(w http.ResponseWriter, r *http.Request) { - urlParts := strings.Split(r.URL.Path, "/") - - sessionID := urlParts[1] - token := urlParts[2] - - session, err := s.sessionFileStore.Read(sessionID) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - exam := session.Exams[token] - - if r.Method == "GET" { - w.Header().Set("Content-Type", "text/html") - - tplData, err := os.ReadFile(filepath.Join(GetDefaultTemplateDir(), "exam.tpl")) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - - } - tmpl := template.Must(template.New("exam").Parse(string(tplData))) - - err = tmpl.Execute(w, ExamTemplateData{exam, session.ID}) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - - if r.Method == "POST" { - err := r.ParseForm() - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - response := new(models.Response) - response.UniqueIDFunc = func() string { - return exam.GetID() - } - - response.Questions = make(map[string]string) - for qID, values := range r.Form { - for _, aID := range values { - response.Questions[qID] = aID - } - } - - _, err = s.responseFileStore.Create(response) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - w.Write([]byte("

Thank you for your response.

")) - return - } - -} - -func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - s.mux.ServeHTTP(w, r) -} - -func generateRandomID() string { - id := "" - for i := 0; i < 6; i++ { - id += strconv.Itoa(rand.Intn(9) + 1) - } - return id -} - -func main() { - slog.SetDefault(slog.New( - tint.NewHandler(os.Stdout, &tint.Options{ - Level: slog.LevelInfo, - TimeFormat: time.Kitchen, - }), - )) - - server, err := NewDefaultServer() - if err != nil { - panic(err) - } - - slog.Info("Probo server started.") - http.ListenAndServe(":8080", server) -} diff --git a/backup/server_test.go b/backup/server_test.go deleted file mode 100644 index 45f6902..0000000 --- a/backup/server_test.go +++ /dev/null @@ -1,289 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log/slog" - "net/http" - "net/http/httptest" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "git.andreafazzi.eu/andrea/probo/pkg/models" - "github.com/lmittmann/tint" - "github.com/remogatto/prettytest" -) - -var examPayload = ` -{ - "ID": "fe0a7ee0-f31a-413d-f123-ab5068bcaaaa", - "Name": "Test session", - "Exams": { - "111222": { - "id": "fe0a7ee0-f31a-413d-ba81-ab5068bc4c73", - "created_at": "0001-01-01T00:00:00Z", - "updated_at": "0001-01-01T00:00:00Z", - "Participant": { - "ID": "1234", - "Firstname": "John", - "Lastname": "Smith", - "Token": "111222", - "Attributes": { - "class": "1 D LIN" - } - }, - "Quizzes": [ - { - "id": "0610939b-a1a3-4d0e-bbc4-2aae0e8ee4b9", - "created_at": "2023-11-27T17:51:53.910642221+01:00", - "updated_at": "0001-01-01T00:00:00Z", - "hash": "", - "question": { - "id": "98c0eec9-677f-464e-9e3e-864a859f29a3", - "created_at": "0001-01-01T00:00:00Z", - "updated_at": "0001-01-01T00:00:00Z", - "text": "Question text with #tag1." - }, - "answers": [ - { - "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", - "text": "Answer 1" - }, - { - "id": "74547724-b905-476f-8cfc-6ee633f92ef3", - "text": "Answer 2" - }, - { - "id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd", - "text": "Answer 3" - }, - { - "id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b", - "text": "Answer 4" - } - ], - "tags": [ - { - "CreatedAt": "0001-01-01T00:00:00Z", - "UpdatedAt": "0001-01-01T00:00:00Z", - "DeletedAt": null, - "name": "#tag1" - } - ], - "correct": { - "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", - "text": "Answer 1" - }, - "CorrectPos": 0, - "type": 0 - }, - { - "id": "915818c4-b0ce-4efc-8def-7fc8d5fa7454", - "created_at": "2023-11-27T17:51:53.91077796+01:00", - "updated_at": "0001-01-01T00:00:00Z", - "hash": "", - "question": { - "id": "70793f0d-2855-4140-814e-40167464424b", - "created_at": "0001-01-01T00:00:00Z", - "updated_at": "0001-01-01T00:00:00Z", - "text": "Another question text with #tag1." - }, - "answers": [ - { - "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", - "text": "Answer 1" - }, - { - "id": "74547724-b905-476f-8cfc-6ee633f92ef3", - "text": "Answer 2" - }, - { - "id": "96c1a8ee-c50c-4ebc-89e4-9f3ca356adbd", - "text": "Answer 3" - }, - { - "id": "16c4b95e-64ce-4666-8cbe-b66fa59eb23b", - "text": "Answer 4" - } - ], - "tags": [ - { - "CreatedAt": "0001-01-01T00:00:00Z", - "UpdatedAt": "0001-01-01T00:00:00Z", - "DeletedAt": null, - "name": "#tag1" - } - ], - "correct": { - "id": "1ab5ff1f-74c8-4efc-abdc-d03a3640f3bc", - "text": "Answer 1" - }, - "CorrectPos": 0, - "type": 0 - } - ] - } - }, - "333444": { - "id": "12345678-abcd-efgh-ijkl-9876543210ef", - "created_at": "2023-12-01T12:00:00Z", - "updated_at": "2023-12-01T12:00:00Z", - "Participant": { - "ID": "5678", - "Firstname": "Jane", - "Lastname": "Doe", - "Token": "333444", - "Attributes": { - "class": "2 A SCI" - } - }, - "Quizzes": [ - { - "id": "22222222-abcd-efgh-ijkl-9876543210ef", - "created_at": "2023-12-01T12:00:00Z", - "updated_at": "2023-12-01T12:00:00Z", - "hash": "", - "question": { - "id": "33333333-abcd-efgh-ijkl-9876543210ef", - "created_at": "2023-12-01T12:00:00Z", - "updated_at": "2023-12-01T12:00:00Z", - "text": "Sample question text." - }, - "answers": [ - { - "id": "44444444-abcd-efgh-ijkl-9876543210ef", - "text": "Option 1" - }, - { - "id": "55555555-abcd-efgh-ijkl-9876543210ef", - "text": "Option 2" - }, - { - "id": "66666666-abcd-efgh-ijkl-9876543210ef", - "text": "Option 3" - }, - { - "id": "77777777-abcd-efgh-ijkl-9876543210ef", - "text": "Option 4" - } - ], - "tags": [ - { - "CreatedAt": "2023-12-01T12:00:00Z", - "UpdatedAt": "2023-12-01T12:00:00Z", - "DeletedAt": null, - "name": "#tag2" - } - ], - "correct": { - "id": "44444444-abcd-efgh-ijkl-9876543210ef", - "text": "Option 1" - }, - "CorrectPos": 0, - "type": 0 - } - ] - } -} -` - -type serverTestSuite struct { - prettytest.Suite -} - -func TestRunner(t *testing.T) { - slog.SetDefault(slog.New( - tint.NewHandler(os.Stderr, &tint.Options{ - Level: slog.LevelError, - TimeFormat: time.Kitchen, - }), - )) - - prettytest.Run( - t, - new(serverTestSuite), - ) -} - -func (t *serverTestSuite) TestCreate() { - - DefaultDataDir = "testdata" - - s, err := NewDefaultServer() - t.Nil(err) - - if !t.Failed() { - request, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(examPayload)) - response := httptest.NewRecorder() - - handler := http.HandlerFunc(s.createExamSessionHandler) - - handler.ServeHTTP(response, request) - - t.Equal(http.StatusOK, response.Code) - - if !t.Failed() { - var session *models.Session - - err := json.Unmarshal(response.Body.Bytes(), &session) - t.Nil(err) - - path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json") - - _, err = os.Stat(path) - t.Nil(err) - - defer os.Remove(path) - - t.Equal("fe0a7ee0-f31a-413d-f123-ab5068bcaaaa", session.ID) - - } - } -} - -func (t *serverTestSuite) TestRead() { - - DefaultDataDir = "testdata" - - s, err := NewDefaultServer() - t.Nil(err) - - if !t.Failed() { - request, _ := http.NewRequest(http.MethodPost, "/create", strings.NewReader(examPayload)) - response := httptest.NewRecorder() - - handler := http.HandlerFunc(s.createExamSessionHandler) - - handler.ServeHTTP(response, request) - - t.Equal(http.StatusOK, response.Code) - - if !t.Failed() { - var session *models.Session - - err := json.Unmarshal(response.Body.Bytes(), &session) - t.Nil(err) - - path := filepath.Join(GetDefaultSessionDir(), "session_fe0a7ee0-f31a-413d-f123-ab5068bcaaaa.json") - _, err = os.Stat(path) - t.Nil(err) - - if !t.Failed() { - defer os.RemoveAll(path) - - request, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/%s/%s", session.ID, "111222"), nil) - response := httptest.NewRecorder() - - handler := http.HandlerFunc(s.getExamHandler) - - handler.ServeHTTP(response, request) - - t.Equal(http.StatusOK, response.Code) - - } - } - } -} diff --git a/cmd.bk/backup/list/delegate.go b/cmd.bk/backup/list/delegate.go deleted file mode 100644 index e8b57e5..0000000 --- a/cmd.bk/backup/list/delegate.go +++ /dev/null @@ -1,89 +0,0 @@ -package list - -import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" -) - -func newItemDelegate(keys *delegateKeyMap) list.DefaultDelegate { - d := list.NewDefaultDelegate() - - d.UpdateFunc = func(msg tea.Msg, m *list.Model) tea.Cmd { - var title string - - if i, ok := m.SelectedItem().(Item); ok { - title = i.Title() - } else { - return nil - } - - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, keys.choose): - return m.NewStatusMessage(statusMessageStyle("You chose " + title)) - - case key.Matches(msg, keys.remove): - index := m.Index() - m.RemoveItem(index) - if len(m.Items()) == 0 { - keys.remove.SetEnabled(false) - } - return m.NewStatusMessage(statusMessageStyle("Deleted " + title)) - } - } - - return nil - } - - help := []key.Binding{keys.choose, keys.remove} - - d.ShortHelpFunc = func() []key.Binding { - return help - } - - d.FullHelpFunc = func() [][]key.Binding { - return [][]key.Binding{help} - } - - return d -} - -type delegateKeyMap struct { - choose key.Binding - remove key.Binding -} - -// Additional short help entries. This satisfies the help.KeyMap interface and -// is entirely optional. -func (d delegateKeyMap) ShortHelp() []key.Binding { - return []key.Binding{ - d.choose, - d.remove, - } -} - -// Additional full help entries. This satisfies the help.KeyMap interface and -// is entirely optional. -func (d delegateKeyMap) FullHelp() [][]key.Binding { - return [][]key.Binding{ - { - d.choose, - d.remove, - }, - } -} - -func newDelegateKeyMap() *delegateKeyMap { - return &delegateKeyMap{ - choose: key.NewBinding( - key.WithKeys("enter"), - key.WithHelp("enter", "choose"), - ), - remove: key.NewBinding( - key.WithKeys("x", "backspace"), - key.WithHelp("x", "delete"), - ), - } -} diff --git a/cmd.bk/backup/list/list.go b/cmd.bk/backup/list/list.go deleted file mode 100644 index 31c6e38..0000000 --- a/cmd.bk/backup/list/list.go +++ /dev/null @@ -1,158 +0,0 @@ -package list - -import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -var ( - appStyle = lipgloss.NewStyle().Padding(1, 2) - - titleStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("#FFFDF5")). - Background(lipgloss.Color("#25A065")). - Padding(0, 1) - - statusMessageStyle = lipgloss.NewStyle(). - Foreground(lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#04B575"}). - Render -) - -type Item struct { - title string - description string -} - -func NewItem(title string, description string) Item { - return Item{title, description} -} - -func (i Item) Title() string { return i.title } -func (i Item) Description() string { return i.description } -func (i Item) FilterValue() string { return i.title } - -type listKeyMap struct { - toggleSpinner key.Binding - toggleTitleBar key.Binding - toggleStatusBar key.Binding - togglePagination key.Binding - toggleHelpMenu key.Binding -} - -func newListKeyMap() *listKeyMap { - return &listKeyMap{ - toggleSpinner: key.NewBinding( - key.WithKeys("s"), - key.WithHelp("s", "toggle spinner"), - ), - toggleTitleBar: key.NewBinding( - key.WithKeys("T"), - key.WithHelp("T", "toggle title"), - ), - toggleStatusBar: key.NewBinding( - key.WithKeys("S"), - key.WithHelp("S", "toggle status"), - ), - togglePagination: key.NewBinding( - key.WithKeys("P"), - key.WithHelp("P", "toggle pagination"), - ), - toggleHelpMenu: key.NewBinding( - key.WithKeys("H"), - key.WithHelp("H", "toggle help"), - ), - } -} - -type model struct { - list list.Model - keys *listKeyMap - delegateKeys *delegateKeyMap -} - -func NewList(title string, items []list.Item) model { - var ( - delegateKeys = newDelegateKeyMap() - listKeys = newListKeyMap() - ) - - // Setup list - delegate := newItemDelegate(delegateKeys) - groceryList := list.New(items, delegate, 0, 0) - groceryList.Title = title - groceryList.Styles.Title = titleStyle - groceryList.AdditionalFullHelpKeys = func() []key.Binding { - return []key.Binding{ - listKeys.toggleSpinner, - listKeys.toggleTitleBar, - listKeys.toggleStatusBar, - listKeys.togglePagination, - listKeys.toggleHelpMenu, - } - } - - return model{ - list: groceryList, - keys: listKeys, - delegateKeys: delegateKeys, - } -} - -func (m model) Init() tea.Cmd { - return tea.EnterAltScreen -} - -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd - - switch msg := msg.(type) { - case tea.WindowSizeMsg: - h, v := appStyle.GetFrameSize() - m.list.SetSize(msg.Width-h, msg.Height-v) - - case tea.KeyMsg: - // Don't match any of the keys below if we're actively filtering. - if m.list.FilterState() == list.Filtering { - break - } - - switch { - case key.Matches(msg, m.keys.toggleSpinner): - cmd := m.list.ToggleSpinner() - return m, cmd - - case key.Matches(msg, m.keys.toggleTitleBar): - v := !m.list.ShowTitle() - m.list.SetShowTitle(v) - m.list.SetShowFilter(v) - m.list.SetFilteringEnabled(v) - return m, nil - - case key.Matches(msg, m.keys.toggleStatusBar): - m.list.SetShowStatusBar(!m.list.ShowStatusBar()) - return m, nil - - case key.Matches(msg, m.keys.togglePagination): - m.list.SetShowPagination(!m.list.ShowPagination()) - return m, nil - - case key.Matches(msg, m.keys.toggleHelpMenu): - m.list.SetShowHelp(!m.list.ShowHelp()) - return m, nil - - } - } - - // This will also call our delegate's update function. - newListModel, cmd := m.list.Update(msg) - m.list = newListModel - cmds = append(cmds, cmd) - - return m, tea.Batch(cmds...) -} - -func (m model) View() string { - return appStyle.Render(m.list.View()) -} diff --git a/cmd.bk/backup/main.go b/cmd.bk/backup/main.go deleted file mode 100644 index f0ddad1..0000000 --- a/cmd.bk/backup/main.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "log" - "log/slog" - "os" - "time" - - "git.andreafazzi.eu/andrea/probo/pkg/store/file" - "github.com/lmittmann/tint" - "github.com/urfave/cli" -) - -func main() { - slog.SetDefault(slog.New( - tint.NewHandler(os.Stdout, &tint.Options{ - Level: slog.LevelInfo, - TimeFormat: time.Kitchen, - }), - )) - - _, err := os.Stat(file.DefaultBaseDir) - if err != nil { - slog.Info("Base directory not found. Creating the folder structure...") - for _, dir := range file.Dirs { - err := os.MkdirAll(dir, os.ModePerm) - if err != nil { - log.Fatal(err) - } - slog.Info("Create", "directory", dir) - } - - slog.Info("Folder structure created without issues.") - os.Exit(0) - } - - app := &cli.App{ - Name: "probo-cli", - Usage: "Quiz Management System for Hackers!", - Commands: []*cli.Command{ - { - Name: "session", - Aliases: []string{"s"}, - Usage: "options for command 'session'", - Subcommands: []*cli.Command{ - { - Name: "push", - Usage: "Create a new exam session", - Action: push, - }, - { - Name: "pull", - Usage: "Download responses from a session", - Action: pull, - }, - { - Name: "scores", - Usage: "Show the scores for the given session", - Action: scores, - }, - }, - }, - { - Name: "participant", - Aliases: []string{"p"}, - Usage: "options for command 'participant'", - Subcommands: []*cli.Command{ - { - Name: "import", - Usage: "Import participants from a CSV file", - Action: importCSV, - }, - }, - }, - }, - } - - if err := app.Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/cmd.bk/backup/participant.go b/cmd.bk/backup/participant.go deleted file mode 100644 index 08bd8df..0000000 --- a/cmd.bk/backup/participant.go +++ /dev/null @@ -1,31 +0,0 @@ -package main - -import ( - "fmt" - "log/slog" - - "git.andreafazzi.eu/andrea/probo/pkg/store/file" - "github.com/urfave/cli/v2" -) - -func importCSV(cCtx *cli.Context) error { - path := cCtx.Args().First() - if path == "" { - return cli.Exit("Path for the CSV file not given.", 1) - } - - pStore, err := file.NewDefaultParticipantFileStore() - if err != nil { - return cli.Exit(fmt.Sprintf("An error occurred: %v", err), 1) - } - - participants, err := pStore.ImportCSV(path) - if err != nil { - return cli.Exit(fmt.Sprintf("An error occurred: %v", err), 1) - } - - slog.Info("Imported participants from csv file", "nParticipants", len(participants)) - - return nil - -} diff --git a/cmd.bk/backup/session.go b/cmd.bk/backup/session.go deleted file mode 100644 index 6e57da7..0000000 --- a/cmd.bk/backup/session.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "fmt" - "log" - - "git.andreafazzi.eu/andrea/probo/cmd/textinput" - "git.andreafazzi.eu/andrea/probo/pkg/sessionmanager" - "git.andreafazzi.eu/andrea/probo/pkg/store/file" - tea "github.com/charmbracelet/bubbletea" - "github.com/urfave/cli/v2" -) - -func push(cCtx *cli.Context) error { - pStore, err := file.NewDefaultParticipantFileStore() - if err != nil { - return cli.Exit(fmt.Sprintf("An error occurred: %v", err), 1) - } - - participants := pStore.ReadAll() - - if len(participants) == 0 { - return cli.Exit("No participants found!", 1) - } - - sessionName := cCtx.Args().First() - - if cCtx.Args().Len() < 1 { - input := textinput.NewTextInput("My exam session") - p := tea.NewProgram(input) - if _, err := p.Run(); err != nil { - log.Fatal(err) - } - sessionName = input.Value() - } - - sStore, err := file.NewDefaultSessionFileStore() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - qStore, err := file.NewDefaultQuizFileStore() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - sm, err := sessionmanager.NewSessionManager( - "http://localhost:8080/", - pStore.Storer, - qStore.Storer, - nil, - nil, - ) - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - session, err := sm.Push(sessionName) - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - _, err = sStore.Create(session) - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - log.Println("Session upload completed with success!") - - for _, p := range pStore.ReadAll() { - log.Printf("http://localhost:8080/%v/%v", session.ID, p.Token) - } - - return nil - -} - -func pull(cCtx *cli.Context) error { - if cCtx.Args().Len() < 1 { - log.Fatalf("Please provide a session ID as first argument of pull.") - } - - rStore, err := file.NewDefaultResponseFileStore() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - pStore, err := file.NewDefaultParticipantFileStore() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - qStore, err := file.NewDefaultQuizFileStore() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - sm, err := sessionmanager.NewSessionManager( - "http://localhost:8080/", - pStore.Storer, - qStore.Storer, - nil, - nil, - ) - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - err = sm.Pull(rStore, cCtx.Args().First()) - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - log.Println("Responses download completed with success!") - - return nil -} - -func scores(cCtx *cli.Context) error { - if cCtx.Args().Len() < 1 { - log.Fatalf("Please provide a session name as first argument of 'score'.") - } - - sStore, err := file.NewDefaultSessionFileStore() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - session, err := sStore.Read(cCtx.Args().First()) - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - rStore, err := file.NewDefaultResponseFileStore() - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - scores, err := sessionmanager.NewScores(rStore, session) - if err != nil { - log.Fatalf("An error occurred: %v", err) - } - - fmt.Println(scores) - - return nil -} diff --git a/cmd.bk/backup/textinput/textinput.go b/cmd.bk/backup/textinput/textinput.go deleted file mode 100644 index 139704e..0000000 --- a/cmd.bk/backup/textinput/textinput.go +++ /dev/null @@ -1,66 +0,0 @@ -package textinput - -import ( - "fmt" - - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" -) - -type ( - errMsg error -) - -type model struct { - textInput textinput.Model - err error -} - -func NewTextInput(placeholder string) *model { - ti := textinput.New() - ti.Placeholder = placeholder - ti.Focus() - ti.CharLimit = 156 - ti.Width = 20 - - return &model{ - textInput: ti, - err: nil, - } -} - -func (m *model) Value() string { - return m.textInput.Value() -} - -func (m *model) Init() tea.Cmd { - return textinput.Blink -} - -func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd - - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.Type { - case tea.KeyEnter, tea.KeyCtrlC, tea.KeyEsc: - return m, tea.Quit - } - - // We handle errors just like any other message - case errMsg: - m.err = msg - return m, nil - } - - m.textInput, cmd = m.textInput.Update(msg) - return m, cmd -} - -func (m *model) View() string { - return fmt.Sprintf( - "\nPlease insert the name of the session\n\n%s\n\n%s", - m.textInput.View(), - "(esc to quit)", - ) + "\n" -} diff --git a/cmd/create.go b/cmd/create.go new file mode 100644 index 0000000..a5e613c --- /dev/null +++ b/cmd/create.go @@ -0,0 +1,40 @@ +/* +Copyright © 2024 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// createCmd represents the create command +var createCmd = &cobra.Command{ + Use: "create", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("create called") + }, +} + +func init() { + rootCmd.AddCommand(createCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // createCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // createCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/filter.go b/cmd/filter.go new file mode 100644 index 0000000..cf602cf --- /dev/null +++ b/cmd/filter.go @@ -0,0 +1,44 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "fmt" + "os" + + "git.andreafazzi.eu/andrea/probo/cmd/filter" + + tea "github.com/charmbracelet/bubbletea" + "github.com/spf13/cobra" +) + +// filterCmd represents the filter command +var filterCmd = &cobra.Command{ + Use: "filter", + Short: "Create a new filter", + Long: "Create a new filter to select quizzes and participants", + Run: createFilter, +} + +func init() { + createCmd.AddCommand(filterCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // filterCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // filterCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} + +func createFilter(cmd *cobra.Command, args []string) { + if _, err := tea.NewProgram(filter.New()).Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } + +} diff --git a/cmd/filter/filter.go b/cmd/filter/filter.go new file mode 100644 index 0000000..b566b6b --- /dev/null +++ b/cmd/filter/filter.go @@ -0,0 +1,207 @@ +package filter + +import ( + "fmt" + + "git.andreafazzi.eu/andrea/probo/pkg/store" + "git.andreafazzi.eu/andrea/probo/pkg/store/file" + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/remogatto/sugarfoam/components/group" + "github.com/remogatto/sugarfoam/components/header" + "github.com/remogatto/sugarfoam/components/statusbar" + "github.com/remogatto/sugarfoam/components/table" + "github.com/remogatto/sugarfoam/components/textinput" + "github.com/remogatto/sugarfoam/components/viewport" + "github.com/remogatto/sugarfoam/layout" + "github.com/remogatto/sugarfoam/layout/tiled" +) + +type Store interface { +} + +type storeLoadedMsg struct { + store file.FileStorer[store.Storable] +} + +type FilterModel struct { + // UI + group *group.Model + help help.Model + statusBar *statusbar.Model + + // Layout + document *layout.Layout + + // Key bindings + bindings *keyBindings + + // Store + store file.FileStorer[store.Storable] + + state int +} + +type keyBindings struct { + group *group.Model + + quit 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, + ) + } + + keys = append( + keys, + 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"), + ), + } +} + +func New() *FilterModel { + textinput := textinput.New( + textinput.WithPlaceholder("Write your jq filter here..."), + ) + + table := table.New() + viewport := viewport.New() + help := help.New() + + group := group.New( + group.WithItems(textinput, viewport, table), + group.WithLayout( + layout.New( + layout.WithStyles(&layout.Styles{Container: lipgloss.NewStyle().Padding(1, 0, 1, 0)}), + layout.WithItem(textinput), + layout.WithItem(tiled.New(viewport, table)), + ), + ), + ) + + bindings := newBindings(group) + statusBar := statusbar.New(bindings) + + header := header.New( + header.WithContent( + lipgloss.NewStyle(). + Bold(true). + Border(lipgloss.NormalBorder(), false, false, true, false). + Render("Participants filter 👫"), + ), + ) + + document := layout.New( + layout.WithStyles(&layout.Styles{Container: lipgloss.NewStyle().Margin(1)}), + layout.WithItem(header), + layout.WithItem(group), + layout.WithItem(statusBar), + ) + + return &FilterModel{ + group: group, + statusBar: statusBar, + document: document, + bindings: bindings, + help: help, + } + +} + +func (m *FilterModel) Init() tea.Cmd { + var cmds []tea.Cmd + + cmds = append(cmds, m.group.Init(), loadParticipantStore()) + + m.group.Focus() + + return tea.Batch(cmds...) +} + +func (m *FilterModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd + + switch msg := msg.(type) { + + case tea.WindowSizeMsg: + m.document.SetSize(msg.Width, msg.Height) + + case tea.KeyMsg: + switch { + case key.Matches(msg, m.bindings.quit): + return m, tea.Quit + + } + case storeLoadedMsg: + m.handleStoreLoaded(msg) + } + + cmds = m.handleState(msg, cmds) + + return m, tea.Batch(cmds...) +} + +func (m *FilterModel) View() string { + return m.document.View() +} + +func (m *FilterModel) handleStoreLoaded(msg tea.Msg) { + storeMsg := msg.(storeLoadedMsg) + m.store = storeMsg.store + m.state = FilterState +} + +func (m *FilterModel) handleState(msg tea.Msg, cmds []tea.Cmd) []tea.Cmd { + _, cmd := m.group.Update(msg) + + cmds = append(cmds, cmd) + + m.statusBar.SetContent( + formats[FilterState][0], + fmt.Sprintf(formats[FilterState][1], len(m.store.ReadAll())), + formats[FilterState][2], + ) + + return cmds +} + +func loadParticipantStore() tea.Cmd { + return func() tea.Msg { + pStore, err := file.NewDefaultParticipantFileStore() + if err != nil { + panic(err) + } + + return storeLoadedMsg{pStore} + } +} diff --git a/cmd/filter/formats.go b/cmd/filter/formats.go new file mode 100644 index 0000000..045b7ef --- /dev/null +++ b/cmd/filter/formats.go @@ -0,0 +1,8 @@ +package filter + +var ( + formats = map[int][]string{ + FilterState: []string{"FILTER 📖", "Currently selected %d elements from the store", "STORE 🟢"}, + LoadingStoreState: []string{"LOAD %S", "Loading the store...", "STORE 🔴"}, + } +) diff --git a/cmd/filter/states.go b/cmd/filter/states.go new file mode 100644 index 0000000..3d3d8e3 --- /dev/null +++ b/cmd/filter/states.go @@ -0,0 +1,6 @@ +package filter + +const ( + LoadingStoreState = iota + FilterState +) diff --git a/cmd/root.go b/cmd/root.go index 6a8555f..922c9d9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -34,13 +34,12 @@ var cfgFile string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "probo", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: + Short: "A Quiz Management System for Hackers!", + Long: ` +Probo is a CLI/TUI application that allows for the quick +creation of quizzes from markdown files and their distribution +as web pages.`, -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, // Uncomment the following line if your bare application // has an action associated with it: // Run: func(cmd *cobra.Command, args []string) { }, diff --git a/cmd/session.go b/cmd/session.go new file mode 100644 index 0000000..a8a00da --- /dev/null +++ b/cmd/session.go @@ -0,0 +1,40 @@ +/* +Copyright © 2024 NAME HERE + +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// sessionCmd represents the session command +var sessionCmd = &cobra.Command{ + Use: "session", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("session called") + }, +} + +func init() { + createCmd.AddCommand(sessionCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // sessionCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // sessionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/go.mod b/go.mod index 4bf98fd..349a38b 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,14 @@ module git.andreafazzi.eu/andrea/probo go 1.21.6 require ( - github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 github.com/charmbracelet/lipgloss v0.9.1 github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a github.com/google/uuid v1.6.0 - github.com/lmittmann/tint v1.0.4 github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 - github.com/urfave/cli v1.22.14 - github.com/urfave/cli/v2 v2.27.1 + github.com/remogatto/sugarfoam v0.0.0-20240206073346-8d2fef68eb8b + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 gopkg.in/yaml.v2 v2.4.0 gorm.io/gorm v1.25.6 ) @@ -19,13 +18,15 @@ require ( require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/charmbracelet/bubbles v0.18.0 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.18 // indirect @@ -38,18 +39,14 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/rivo/uniseg v0.4.6 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.18.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect diff --git a/go.sum b/go.sum index d78609b..470a4c0 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -11,18 +10,20 @@ github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1 github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA= github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= @@ -33,10 +34,10 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= -github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -60,23 +61,25 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 h1:nRDwTcxV9B3elxMt+1xINX0bwaPdpouqp5fbynexY8U= github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8/go.mod h1:jOEnp79oIHy5cvQSHeLcgVJk1GHOOHJHQWps/d1N5Yo= +github.com/remogatto/sugarfoam v0.0.0-20240206073346-8d2fef68eb8b h1:GY4q+f7GZo6c8aeLKsXFu5gpIG+EgoWSyPvz7qmP+K8= +github.com/remogatto/sugarfoam v0.0.0-20240206073346-8d2fef68eb8b/go.mod h1:wRQC/69u0SRWrXFi8360WNbzrWq70mVoUxY7v8Uykw8= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= -github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -99,35 +102,24 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= -github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= -github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/go.work b/go.work index c4eec06..d959651 100644 --- a/go.work +++ b/go.work @@ -1,3 +1,6 @@ go 1.21.6 -use . +use ( + . + ../sugarfoam +) diff --git a/go.work.sum b/go.work.sum index ffd7cfb..c75b1b1 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,34 +1,82 @@ +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ= +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/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +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= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= +github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/sagikazarmark/crypt v0.17.0/go.mod h1:SMtHTvdmsZMuY/bpZoqokSoChIrcJ/epOxZN58PbZDg= +github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f h1:MvTmaQdww/z0Q4wrYjDSCcZ78NoftLQyHBSLW/Cx79Y= +github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= +go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= +go.etcd.io/etcd/client/v2 v2.305.10/go.mod h1:m3CKZi69HzilhVqtPDcjhSGp+kA1OmbNn0qamH80xjA= +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.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +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/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= -golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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/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= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= diff --git a/pkg/store/file/exam_test.go b/pkg/store/file/exam_test.go index 0cbee5c..879e99e 100644 --- a/pkg/store/file/exam_test.go +++ b/pkg/store/file/exam_test.go @@ -7,7 +7,6 @@ import ( "os" "git.andreafazzi.eu/andrea/probo/pkg/models" - "git.andreafazzi.eu/andrea/probo/pkg/store" "github.com/remogatto/prettytest" ) @@ -15,67 +14,67 @@ type examTestSuite struct { prettytest.Suite } -func (t *examTestSuite) TestCreate() { - participantStore, err := NewParticipantFileStore( - &FileStoreConfig[*models.Participant, *store.ParticipantStore]{ - FilePathConfig: FilePathConfig{"testdata/exams/participants", "participant", ".json"}, - IndexDirFunc: DefaultIndexDirFunc[*models.Participant, *store.ParticipantStore], - CreateEntityFunc: func() *models.Participant { - return &models.Participant{ - Attributes: make(map[string]string), - } - }, - }, - ) +// func (t *examTestSuite) TestCreate() { +// participantStore, err := NewParticipantFileStore( +// &FileStoreConfig[*models.Participant, *store.ParticipantStore]{ +// FilePathConfig: FilePathConfig{"testdata/exams/participants", "participant", ".json"}, +// IndexDirFunc: DefaultIndexDirFunc[*models.Participant, *store.ParticipantStore], +// CreateEntityFunc: func() *models.Participant { +// return &models.Participant{ +// Attributes: make(map[string]string), +// } +// }, +// }, +// ) - t.Nil(err) +// t.Nil(err) - quizStore, err := NewQuizFileStore( - &FileStoreConfig[*models.Quiz, *store.QuizStore]{ - FilePathConfig: FilePathConfig{"testdata/exams/quizzes", "quiz", ".md"}, - IndexDirFunc: DefaultQuizIndexDirFunc, - }, - ) +// quizStore, err := NewQuizFileStore( +// &FileStoreConfig[*models.Quiz, *store.QuizStore]{ +// FilePathConfig: FilePathConfig{"testdata/exams/quizzes", "quiz", ".md"}, +// IndexDirFunc: DefaultQuizIndexDirFunc, +// }, +// ) - t.Nil(err) +// t.Nil(err) - if !t.Failed() { - t.Equal(3, len(participantStore.ReadAll())) +// if !t.Failed() { +// t.Equal(3, len(participantStore.ReadAll())) - examStore, err := NewDefaultExamFileStore() - t.Nil(err) +// examStore, err := NewDefaultExamFileStore() +// t.Nil(err) - if !t.Failed() { - g := new(models.Group) - c := new(models.Collection) +// if !t.Failed() { +// g := new(models.Group) +// c := new(models.Collection) - participants := participantStore.Storer.FilterInGroup(g, map[string]string{"class": "1 D LIN"}) - quizzes := quizStore.Storer.FilterInCollection(c, map[string]string{"tags": "#tag1"}) +// participants := participantStore.Storer.FilterInGroup(g, map[string]string{"class": "1 D LIN"}) +// quizzes := quizStore.Storer.FilterInCollection(c, map[string]string{"tags": "#tag1"}) - for _, p := range participants { - e := new(models.Exam) - e.Participant = p - e.Quizzes = quizzes +// for _, p := range participants { +// e := new(models.Exam) +// e.Participant = p +// e.Quizzes = quizzes - _, err = examStore.Create(e) - t.Nil(err) +// _, err = examStore.Create(e) +// t.Nil(err) - defer os.Remove(examStore.GetPath(e)) +// defer os.Remove(examStore.GetPath(e)) - examFromDisk, err := readExamFromJSON(e.GetID()) - t.Nil(err) +// examFromDisk, err := readExamFromJSON(e.GetID()) +// t.Nil(err) - if !t.Failed() { - t.Not(t.Nil(examFromDisk.Participant)) - if !t.Failed() { - t.Equal("Smith", examFromDisk.Participant.Lastname) - t.Equal(2, len(examFromDisk.Quizzes)) - } - } - } - } - } -} +// if !t.Failed() { +// t.Not(t.Nil(examFromDisk.Participant)) +// if !t.Failed() { +// t.Equal("Smith", examFromDisk.Participant.Lastname) +// t.Equal(2, len(examFromDisk.Quizzes)) +// } +// } +// } +// } +// } +// } func readExamFromJSON(examID string) (*models.Exam, error) { // Build the path to the JSON file diff --git a/pkg/store/file/file.go b/pkg/store/file/file.go index b9f8943..805c58d 100644 --- a/pkg/store/file/file.go +++ b/pkg/store/file/file.go @@ -12,7 +12,7 @@ import ( "git.andreafazzi.eu/andrea/probo/pkg/store" ) -type IndexDirFunc[T FileStorable, K Storer[T]] func(s *FileStore[T, K]) error +type IndexDirFunc[T FileStorable, K store.Storer[T]] func(s *FileStore[T, K]) error var ( ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.") @@ -25,8 +25,13 @@ type FileStorable interface { Unmarshal([]byte) error } -type Storer[T store.Storable] interface { - store.Storer[T] +type FileStorer[T store.Storable] interface { + // store.Storer[T] + Create(T, ...string) (T, error) + ReadAll() []T + Read(string) (T, error) + Update(T, string) (T, error) + Delete(string) (T, error) } type FilePathConfig struct { @@ -35,14 +40,14 @@ type FilePathConfig struct { FileSuffix string } -type FileStoreConfig[T FileStorable, K Storer[T]] struct { +type FileStoreConfig[T FileStorable, K store.Storer[T]] struct { FilePathConfig IndexDirFunc func(*FileStore[T, K]) error CreateEntityFunc func() T NoIndexOnCreate bool } -type FileStore[T FileStorable, K Storer[T]] struct { +type FileStore[T FileStorable, K store.Storer[T]] struct { *FileStoreConfig[T, K] Storer K @@ -51,7 +56,7 @@ type FileStore[T FileStorable, K Storer[T]] struct { paths map[string]string } -func DefaultIndexDirFunc[T FileStorable, K Storer[T]](s *FileStore[T, K]) error { +func DefaultIndexDirFunc[T FileStorable, K store.Storer[T]](s *FileStore[T, K]) error { if s.CreateEntityFunc == nil { return errors.New("CreateEntityFunc cannot be nil!") } @@ -98,7 +103,7 @@ func DefaultIndexDirFunc[T FileStorable, K Storer[T]](s *FileStore[T, K]) error } -func NewFileStore[T FileStorable, K Storer[T]](config *FileStoreConfig[T, K], storer K) (*FileStore[T, K], error) { +func NewFileStore[T FileStorable, K store.Storer[T]](config *FileStoreConfig[T, K], storer K) (*FileStore[T, K], error) { store := &FileStore[T, K]{ FileStoreConfig: config, Storer: storer, diff --git a/pkg/store/participant.go b/pkg/store/participant.go index b43b636..7fd3580 100644 --- a/pkg/store/participant.go +++ b/pkg/store/participant.go @@ -60,27 +60,3 @@ func generateToken() string { return token } - -// func (s *ParticipantStore) FilterInGroup(group *models.Group, filter map[string]string) []*models.Participant { -// participants := s.ReadAll() - -// if filter == nil { -// return participants -// } - -// filteredParticipants := s.Filter(participants, func(p *models.Participant) bool { -// for pk, pv := range p.Attributes { -// for fk, fv := range filter { -// if pk == fk && pv == fv { -// return true -// } -// } -// } - -// return false -// }) - -// group.Participants = filteredParticipants - -// return group.Participants -// }