From b8120899c1116827f27b4a25df8cbea35f751cf8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 4 Dec 2024 17:09:27 -0800 Subject: [PATCH] Added a general print function. --- cmd/call-api-endpoint/expected_output_test.go | 22 ----- cmd/call-endpoint/expected_output_test.go | 22 +++++ .../main.go | 86 ++++++++++++++++--- .../main_test.go | 10 --- internal/cmd/custom_output.go | 67 --------------- internal/cmd/request.go | 7 +- 6 files changed, 101 insertions(+), 113 deletions(-) delete mode 100644 cmd/call-api-endpoint/expected_output_test.go create mode 100644 cmd/call-endpoint/expected_output_test.go rename cmd/{call-api-endpoint => call-endpoint}/main.go (59%) rename cmd/{call-api-endpoint => call-endpoint}/main_test.go (93%) delete mode 100644 internal/cmd/custom_output.go diff --git a/cmd/call-api-endpoint/expected_output_test.go b/cmd/call-api-endpoint/expected_output_test.go deleted file mode 100644 index 766a8368..00000000 --- a/cmd/call-api-endpoint/expected_output_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package main - -const EXPECTED_SERVER_USER_LIST_TABLE = `email name server-role courses -course-admin@test.edulinq.org course-admin user {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"admin"},"course101":{"id":"course101","name":"Course 101","role":"admin"}} -course-grader@test.edulinq.org course-grader user {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"grader"},"course101":{"id":"course101","name":"Course 101","role":"grader"}} -course-other@test.edulinq.org course-other user {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"other"},"course101":{"id":"course101","name":"Course 101","role":"other"}} -course-owner@test.edulinq.org course-owner user {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"owner"},"course101":{"id":"course101","name":"Course 101","role":"owner"}} -course-student@test.edulinq.org course-student user {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"student"},"course101":{"id":"course101","name":"Course 101","role":"student"}} -root root root {} -server-admin@test.edulinq.org server-admin admin {} -server-creator@test.edulinq.org server-creator creator {} -server-owner@test.edulinq.org server-owner owner {} -server-user@test.edulinq.org server-user user {} -` - -const EXPECTED_COURSE_USER_LIST_TABLE = `email name role lms-id -course-admin@test.edulinq.org course-admin admin lms-course-admin@test.edulinq.org -course-grader@test.edulinq.org course-grader grader lms-course-grader@test.edulinq.org -course-other@test.edulinq.org course-other other lms-course-other@test.edulinq.org -course-owner@test.edulinq.org course-owner owner lms-course-owner@test.edulinq.org -course-student@test.edulinq.org course-student student lms-course-student@test.edulinq.org -` diff --git a/cmd/call-endpoint/expected_output_test.go b/cmd/call-endpoint/expected_output_test.go new file mode 100644 index 00000000..39e63759 --- /dev/null +++ b/cmd/call-endpoint/expected_output_test.go @@ -0,0 +1,22 @@ +package main + +const EXPECTED_SERVER_USER_LIST_TABLE = `email name role type courses +course-admin@test.edulinq.org course-admin user server {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"admin"},"course101":{"id":"course101","name":"Course 101","role":"admin"}} +course-grader@test.edulinq.org course-grader user server {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"grader"},"course101":{"id":"course101","name":"Course 101","role":"grader"}} +course-other@test.edulinq.org course-other user server {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"other"},"course101":{"id":"course101","name":"Course 101","role":"other"}} +course-owner@test.edulinq.org course-owner user server {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"owner"},"course101":{"id":"course101","name":"Course 101","role":"owner"}} +course-student@test.edulinq.org course-student user server {"course-languages":{"id":"course-languages","name":"Course Using Different Languages.","role":"student"},"course101":{"id":"course101","name":"Course 101","role":"student"}} +root root root server {} +server-admin@test.edulinq.org server-admin admin server {} +server-creator@test.edulinq.org server-creator creator server {} +server-owner@test.edulinq.org server-owner owner server {} +server-user@test.edulinq.org server-user user server {} +` + +const EXPECTED_COURSE_USER_LIST_TABLE = `email lms-id name role type +course-admin@test.edulinq.org lms-course-admin@test.edulinq.org course-admin admin course +course-grader@test.edulinq.org lms-course-grader@test.edulinq.org course-grader grader course +course-other@test.edulinq.org lms-course-other@test.edulinq.org course-other other course +course-owner@test.edulinq.org lms-course-owner@test.edulinq.org course-owner owner course +course-student@test.edulinq.org lms-course-student@test.edulinq.org course-student student course +` diff --git a/cmd/call-api-endpoint/main.go b/cmd/call-endpoint/main.go similarity index 59% rename from cmd/call-api-endpoint/main.go rename to cmd/call-endpoint/main.go index b2bbe119..89ed8139 100644 --- a/cmd/call-api-endpoint/main.go +++ b/cmd/call-endpoint/main.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "sort" "strings" "github.com/alecthomas/kong" @@ -11,6 +12,7 @@ import ( "github.com/edulinq/autograder/internal/cmd" "github.com/edulinq/autograder/internal/config" "github.com/edulinq/autograder/internal/log" + "github.com/edulinq/autograder/internal/util" ) var args struct { @@ -19,9 +21,15 @@ var args struct { Endpoint string `help:"Endpoint of the desired API." arg:""` Parameters []string `help:"Parameter for the endpoint in the format 'key:value', e.g., 'id:123'." arg:"" optional:""` - Table bool `help:"Output data as a TSV (only supported for specific endpoints; see help for more info)." default:"false"` + Table bool `help:"Attempt to output data as a TSV. Will default to JSON." default:"false"` } +const ( + USERS = "users" + COURSES = "courses" + TYPE = "type" +) + func main() { kong.Parse(&args, kong.Description(generateHelpDescription()), @@ -68,13 +76,7 @@ func main() { var printFunc cmd.CustomResponseFormatter if args.Table { - printFunc = cmd.EndpointCustomFormatters[args.Endpoint] - if printFunc == nil { - log.Fatal("Table formatting is not supported for the specified endpoint.", log.NewAttr("endpoint", args.Endpoint)) - - // Return to prevent further execution after log.Fatal(). - return - } + printFunc = printCMDResponseTable } cmd.MustHandleCMDRequestAndExitFull(args.Endpoint, request, nil, args.CommonOptions, printFunc) @@ -91,12 +93,70 @@ func generateHelpDescription() string { endpointList.WriteString(fmt.Sprintf(" - %s\n", endpoint)) } - var customOutputEndpointList strings.Builder - customOutputEndpointList.WriteString("Endpoints supporting TSV formatting:\n") + return baseDescription + endpointList.String() +} + +func printCMDResponseTable(response core.APIResponse) string { + responseContent, ok := response.Content.(map[string]any) + if !ok { + return "" + } + + users, ok := responseContent[USERS].([]any) + if !ok { + return "" + } + + firstUser, ok := users[0].(map[string]any) + if !ok { + return "" + } + + var headers []string + for key := range firstUser { + if key == COURSES { + continue + } + headers = append(headers, key) + } + + sort.Strings(headers) + + // Add courses to the end of the slice for better readability in the output. + _, exists := firstUser[COURSES] + if exists { + headers = append(headers, COURSES) + } + + var usersTable strings.Builder + usersTable.WriteString(strings.Join(headers, "\t")) + + lines := strings.Split(usersTable.String(), "\t") - for endpoint := range cmd.EndpointCustomFormatters { - customOutputEndpointList.WriteString(fmt.Sprintf(" - %s\n", endpoint)) + usersTable.WriteString("\n") + + for i, user := range users { + userMap, ok := user.(map[string]any) + if !ok { + return "" + } + + var row []string + for _, key := range lines { + switch value := userMap[key].(type) { + case string: + row = append(row, value) + default: + row = append(row, util.MustToJSON(value)) + } + } + + usersTable.WriteString(strings.Join(row, "\t")) + + if i < len(users)-1 { + usersTable.WriteString("\n") + } } - return baseDescription + endpointList.String() + customOutputEndpointList.String() + return usersTable.String() } diff --git a/cmd/call-api-endpoint/main_test.go b/cmd/call-endpoint/main_test.go similarity index 93% rename from cmd/call-api-endpoint/main_test.go rename to cmd/call-endpoint/main_test.go index cd871c98..bff8e3b2 100644 --- a/cmd/call-api-endpoint/main_test.go +++ b/cmd/call-endpoint/main_test.go @@ -39,16 +39,6 @@ func TestCallApiEndpointBase(test *testing.T) { "target-submission:1697406256", }, }, - { - CommonCMDTestCase: cmd.CommonCMDTestCase{ - ExpectedStderrSubstring: `Table formatting is not supported for the specified endpoint. | {"endpoint":"courses/assignments/submissions/fetch/user/peek"}`, - ExpectedExitCode: 1, - }, - endpoint: "courses/assignments/submissions/fetch/user/peek", - parameters: []string{ - "--table", - }, - }, // Custom Output Formatters. { diff --git a/internal/cmd/custom_output.go b/internal/cmd/custom_output.go deleted file mode 100644 index ec6c54b2..00000000 --- a/internal/cmd/custom_output.go +++ /dev/null @@ -1,67 +0,0 @@ -package cmd - -import ( - "strings" - - "github.com/edulinq/autograder/internal/api/core" - courseUsers "github.com/edulinq/autograder/internal/api/courses/users" - "github.com/edulinq/autograder/internal/api/users" - "github.com/edulinq/autograder/internal/util" -) - -var EndpointCustomFormatters = map[string]CustomResponseFormatter{ - "users/list": mustListServerUsersTable, - "courses/users/list": mustListCourseUsersTable, -} - -func mustListCourseUsersTable(response core.APIResponse) string { - var responseContent courseUsers.ListResponse - util.MustJSONFromString(util.MustToJSON(response.Content), &responseContent) - - var courseUsersTable strings.Builder - - headers := []string{"email", "name", "role", "lms-id"} - courseUsersTable.WriteString(strings.Join(headers, "\t") + "\n") - - for i, user := range responseContent.Users { - if i > 0 { - courseUsersTable.WriteString("\n") - } - - courseUsersTable.WriteString(user.Email) - courseUsersTable.WriteString("\t") - courseUsersTable.WriteString(user.Name) - courseUsersTable.WriteString("\t") - courseUsersTable.WriteString(user.Role.String()) - courseUsersTable.WriteString("\t") - courseUsersTable.WriteString(user.LMSID) - } - - return courseUsersTable.String() -} - -func mustListServerUsersTable(response core.APIResponse) string { - var responseContent users.ListResponse - util.MustJSONFromString(util.MustToJSON(response.Content), &responseContent) - - var serverUsersTable strings.Builder - - headers := []string{"email", "name", "server-role", "courses"} - serverUsersTable.WriteString(strings.Join(headers, "\t") + "\n") - - for i, user := range responseContent.Users { - if i > 0 { - serverUsersTable.WriteString("\n") - } - - serverUsersTable.WriteString(user.Email) - serverUsersTable.WriteString("\t") - serverUsersTable.WriteString(user.Name) - serverUsersTable.WriteString("\t") - serverUsersTable.WriteString(user.Role.String()) - serverUsersTable.WriteString("\t") - serverUsersTable.WriteString(util.MustToJSON(user.Courses)) - } - - return serverUsersTable.String() -} diff --git a/internal/cmd/request.go b/internal/cmd/request.go index 687c10a3..3d58ee4d 100644 --- a/internal/cmd/request.go +++ b/internal/cmd/request.go @@ -105,8 +105,13 @@ func PrintCMDResponseFull(request any, response core.APIResponse, responseType a fmt.Printf("\nAutograder Response:\n---\n%s\n---\n", util.MustToJSONIndent(response)) } + customOutput := "" if customPrintFunc != nil { - fmt.Println(customPrintFunc(response)) + customOutput = customPrintFunc(response) + } + + if len(customOutput) > 0 { + fmt.Println(customOutput) } else if responseType == nil { fmt.Println(util.MustToJSONIndent(response.Content)) } else {