Use sugarfoam
This commit is contained in:
parent
b50932124a
commit
e72e79d1f7
23 changed files with 500 additions and 1282 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1 +1,3 @@
|
||||||
.log
|
.log
|
||||||
|
data
|
||||||
|
|
||||||
|
|
290
backup/main.go
290
backup/main.go
|
@ -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("<p>Thank you for your response.</p>"))
|
|
||||||
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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
40
cmd/create.go
Normal file
40
cmd/create.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
|
||||||
|
|
||||||
|
*/
|
||||||
|
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")
|
||||||
|
}
|
44
cmd/filter.go
Normal file
44
cmd/filter.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
207
cmd/filter/filter.go
Normal file
207
cmd/filter/filter.go
Normal file
|
@ -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}
|
||||||
|
}
|
||||||
|
}
|
8
cmd/filter/formats.go
Normal file
8
cmd/filter/formats.go
Normal file
|
@ -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 🔴"},
|
||||||
|
}
|
||||||
|
)
|
6
cmd/filter/states.go
Normal file
6
cmd/filter/states.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package filter
|
||||||
|
|
||||||
|
const (
|
||||||
|
LoadingStoreState = iota
|
||||||
|
FilterState
|
||||||
|
)
|
11
cmd/root.go
11
cmd/root.go
|
@ -34,13 +34,12 @@ var cfgFile string
|
||||||
// rootCmd represents the base command when called without any subcommands
|
// rootCmd represents the base command when called without any subcommands
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "probo",
|
Use: "probo",
|
||||||
Short: "A brief description of your application",
|
Short: "A Quiz Management System for Hackers!",
|
||||||
Long: `A longer description that spans multiple lines and likely contains
|
Long: `
|
||||||
examples and usage of using your application. For example:
|
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
|
// Uncomment the following line if your bare application
|
||||||
// has an action associated with it:
|
// has an action associated with it:
|
||||||
// Run: func(cmd *cobra.Command, args []string) { },
|
// Run: func(cmd *cobra.Command, args []string) { },
|
||||||
|
|
40
cmd/session.go
Normal file
40
cmd/session.go
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
|
||||||
|
|
||||||
|
*/
|
||||||
|
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")
|
||||||
|
}
|
17
go.mod
17
go.mod
|
@ -3,15 +3,14 @@ module git.andreafazzi.eu/andrea/probo
|
||||||
go 1.21.6
|
go 1.21.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/bubbles v0.18.0
|
|
||||||
github.com/charmbracelet/bubbletea v0.25.0
|
github.com/charmbracelet/bubbletea v0.25.0
|
||||||
github.com/charmbracelet/lipgloss v0.9.1
|
github.com/charmbracelet/lipgloss v0.9.1
|
||||||
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
|
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
|
||||||
github.com/google/uuid v1.6.0
|
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/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8
|
||||||
github.com/urfave/cli v1.22.14
|
github.com/remogatto/sugarfoam v0.0.0-20240206073346-8d2fef68eb8b
|
||||||
github.com/urfave/cli/v2 v2.27.1
|
github.com/spf13/cobra v1.8.0
|
||||||
|
github.com/spf13/viper v1.18.2
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gorm.io/gorm v1.25.6
|
gorm.io/gorm v1.25.6
|
||||||
)
|
)
|
||||||
|
@ -19,13 +18,15 @@ require (
|
||||||
require (
|
require (
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // 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/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/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // 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/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.18 // 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/muesli/termenv v0.15.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
github.com/rivo/uniseg v0.4.6 // 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/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.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/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.11.0 // indirect
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
github.com/spf13/cast v1.6.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/pflag v1.0.5 // indirect
|
||||||
github.com/spf13/viper v1.18.2 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // 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/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
|
|
44
go.sum
44
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 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
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/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 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
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/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.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.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 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 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
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 h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA=
|
||||||
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
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/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 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
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 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
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/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 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
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.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 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 h1:nRDwTcxV9B3elxMt+1xINX0bwaPdpouqp5fbynexY8U=
|
||||||
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8/go.mod h1:jOEnp79oIHy5cvQSHeLcgVJk1GHOOHJHQWps/d1N5Yo=
|
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.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.2.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 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
|
||||||
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
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/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 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
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 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
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 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
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/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 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
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 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
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 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
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 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
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.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.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 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
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 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
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 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
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 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/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 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
|
5
go.work
5
go.work
|
@ -1,3 +1,6 @@
|
||||||
go 1.21.6
|
go 1.21.6
|
||||||
|
|
||||||
use .
|
use (
|
||||||
|
.
|
||||||
|
../sugarfoam
|
||||||
|
)
|
||||||
|
|
68
go.work.sum
68
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/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-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/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/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/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/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/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-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-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/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/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/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/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
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/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
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/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/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/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/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/sagikazarmark/crypt v0.17.0/go.mod h1:SMtHTvdmsZMuY/bpZoqokSoChIrcJ/epOxZN58PbZDg=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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/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/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.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.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/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=
|
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=
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.andreafazzi.eu/andrea/probo/pkg/models"
|
"git.andreafazzi.eu/andrea/probo/pkg/models"
|
||||||
"git.andreafazzi.eu/andrea/probo/pkg/store"
|
|
||||||
"github.com/remogatto/prettytest"
|
"github.com/remogatto/prettytest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,67 +14,67 @@ type examTestSuite struct {
|
||||||
prettytest.Suite
|
prettytest.Suite
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *examTestSuite) TestCreate() {
|
// func (t *examTestSuite) TestCreate() {
|
||||||
participantStore, err := NewParticipantFileStore(
|
// participantStore, err := NewParticipantFileStore(
|
||||||
&FileStoreConfig[*models.Participant, *store.ParticipantStore]{
|
// &FileStoreConfig[*models.Participant, *store.ParticipantStore]{
|
||||||
FilePathConfig: FilePathConfig{"testdata/exams/participants", "participant", ".json"},
|
// FilePathConfig: FilePathConfig{"testdata/exams/participants", "participant", ".json"},
|
||||||
IndexDirFunc: DefaultIndexDirFunc[*models.Participant, *store.ParticipantStore],
|
// IndexDirFunc: DefaultIndexDirFunc[*models.Participant, *store.ParticipantStore],
|
||||||
CreateEntityFunc: func() *models.Participant {
|
// CreateEntityFunc: func() *models.Participant {
|
||||||
return &models.Participant{
|
// return &models.Participant{
|
||||||
Attributes: make(map[string]string),
|
// Attributes: make(map[string]string),
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
)
|
// )
|
||||||
|
|
||||||
t.Nil(err)
|
// t.Nil(err)
|
||||||
|
|
||||||
quizStore, err := NewQuizFileStore(
|
// quizStore, err := NewQuizFileStore(
|
||||||
&FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
// &FileStoreConfig[*models.Quiz, *store.QuizStore]{
|
||||||
FilePathConfig: FilePathConfig{"testdata/exams/quizzes", "quiz", ".md"},
|
// FilePathConfig: FilePathConfig{"testdata/exams/quizzes", "quiz", ".md"},
|
||||||
IndexDirFunc: DefaultQuizIndexDirFunc,
|
// IndexDirFunc: DefaultQuizIndexDirFunc,
|
||||||
},
|
// },
|
||||||
)
|
// )
|
||||||
|
|
||||||
t.Nil(err)
|
// t.Nil(err)
|
||||||
|
|
||||||
if !t.Failed() {
|
// if !t.Failed() {
|
||||||
t.Equal(3, len(participantStore.ReadAll()))
|
// t.Equal(3, len(participantStore.ReadAll()))
|
||||||
|
|
||||||
examStore, err := NewDefaultExamFileStore()
|
// examStore, err := NewDefaultExamFileStore()
|
||||||
t.Nil(err)
|
// t.Nil(err)
|
||||||
|
|
||||||
if !t.Failed() {
|
// if !t.Failed() {
|
||||||
g := new(models.Group)
|
// g := new(models.Group)
|
||||||
c := new(models.Collection)
|
// c := new(models.Collection)
|
||||||
|
|
||||||
participants := participantStore.Storer.FilterInGroup(g, map[string]string{"class": "1 D LIN"})
|
// participants := participantStore.Storer.FilterInGroup(g, map[string]string{"class": "1 D LIN"})
|
||||||
quizzes := quizStore.Storer.FilterInCollection(c, map[string]string{"tags": "#tag1"})
|
// quizzes := quizStore.Storer.FilterInCollection(c, map[string]string{"tags": "#tag1"})
|
||||||
|
|
||||||
for _, p := range participants {
|
// for _, p := range participants {
|
||||||
e := new(models.Exam)
|
// e := new(models.Exam)
|
||||||
e.Participant = p
|
// e.Participant = p
|
||||||
e.Quizzes = quizzes
|
// e.Quizzes = quizzes
|
||||||
|
|
||||||
_, err = examStore.Create(e)
|
// _, err = examStore.Create(e)
|
||||||
t.Nil(err)
|
// t.Nil(err)
|
||||||
|
|
||||||
defer os.Remove(examStore.GetPath(e))
|
// defer os.Remove(examStore.GetPath(e))
|
||||||
|
|
||||||
examFromDisk, err := readExamFromJSON(e.GetID())
|
// examFromDisk, err := readExamFromJSON(e.GetID())
|
||||||
t.Nil(err)
|
// t.Nil(err)
|
||||||
|
|
||||||
if !t.Failed() {
|
// if !t.Failed() {
|
||||||
t.Not(t.Nil(examFromDisk.Participant))
|
// t.Not(t.Nil(examFromDisk.Participant))
|
||||||
if !t.Failed() {
|
// if !t.Failed() {
|
||||||
t.Equal("Smith", examFromDisk.Participant.Lastname)
|
// t.Equal("Smith", examFromDisk.Participant.Lastname)
|
||||||
t.Equal(2, len(examFromDisk.Quizzes))
|
// t.Equal(2, len(examFromDisk.Quizzes))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func readExamFromJSON(examID string) (*models.Exam, error) {
|
func readExamFromJSON(examID string) (*models.Exam, error) {
|
||||||
// Build the path to the JSON file
|
// Build the path to the JSON file
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"git.andreafazzi.eu/andrea/probo/pkg/store"
|
"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 (
|
var (
|
||||||
ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
|
ErrorMetaHeaderIsNotPresent = errors.New("Meta header was not found in file.")
|
||||||
|
@ -25,8 +25,13 @@ type FileStorable interface {
|
||||||
Unmarshal([]byte) error
|
Unmarshal([]byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Storer[T store.Storable] interface {
|
type FileStorer[T store.Storable] interface {
|
||||||
store.Storer[T]
|
// 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 {
|
type FilePathConfig struct {
|
||||||
|
@ -35,14 +40,14 @@ type FilePathConfig struct {
|
||||||
FileSuffix string
|
FileSuffix string
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileStoreConfig[T FileStorable, K Storer[T]] struct {
|
type FileStoreConfig[T FileStorable, K store.Storer[T]] struct {
|
||||||
FilePathConfig
|
FilePathConfig
|
||||||
IndexDirFunc func(*FileStore[T, K]) error
|
IndexDirFunc func(*FileStore[T, K]) error
|
||||||
CreateEntityFunc func() T
|
CreateEntityFunc func() T
|
||||||
NoIndexOnCreate bool
|
NoIndexOnCreate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileStore[T FileStorable, K Storer[T]] struct {
|
type FileStore[T FileStorable, K store.Storer[T]] struct {
|
||||||
*FileStoreConfig[T, K]
|
*FileStoreConfig[T, K]
|
||||||
|
|
||||||
Storer K
|
Storer K
|
||||||
|
@ -51,7 +56,7 @@ type FileStore[T FileStorable, K Storer[T]] struct {
|
||||||
paths map[string]string
|
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 {
|
if s.CreateEntityFunc == nil {
|
||||||
return errors.New("CreateEntityFunc cannot be 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]{
|
store := &FileStore[T, K]{
|
||||||
FileStoreConfig: config,
|
FileStoreConfig: config,
|
||||||
Storer: storer,
|
Storer: storer,
|
||||||
|
|
|
@ -60,27 +60,3 @@ func generateToken() string {
|
||||||
|
|
||||||
return token
|
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
|
|
||||||
// }
|
|
||||||
|
|
Loading…
Reference in a new issue