diff --git a/internal/exporters/docx.go b/internal/exporters/docx.go index 8c87114..56db39c 100644 --- a/internal/exporters/docx.go +++ b/internal/exporters/docx.go @@ -8,11 +8,12 @@ import ( "strings" "github.com/fumiama/go-docx" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "github.com/kjanat/articulate-parser/internal/interfaces" "github.com/kjanat/articulate-parser/internal/models" "github.com/kjanat/articulate-parser/internal/services" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) // DocxExporter implements the Exporter interface for DOCX format. diff --git a/internal/exporters/docx_test.go b/internal/exporters/docx_test.go index 8c747da..374fcbb 100644 --- a/internal/exporters/docx_test.go +++ b/internal/exporters/docx_test.go @@ -90,7 +90,6 @@ func TestDocxExporter_Export_AddDocxExtension(t *testing.T) { err := exporter.Export(testCourse, outputPath) if err != nil { - t.Fatalf("Export failed: %v", err) } @@ -155,7 +154,6 @@ func TestDocxExporter_ExportLesson(t *testing.T) { err := exporter.Export(course, outputPath) if err != nil { - t.Fatalf("Export failed: %v", err) } @@ -222,7 +220,6 @@ func TestDocxExporter_ExportItem(t *testing.T) { err := exporter.Export(course, outputPath) if err != nil { - t.Fatalf("Export failed: %v", err) } @@ -276,7 +273,6 @@ func TestDocxExporter_ExportSubItem(t *testing.T) { err := exporter.Export(course, outputPath) if err != nil { - t.Fatalf("Export failed: %v", err) } @@ -409,7 +405,6 @@ func TestDocxExporter_ComplexCourse(t *testing.T) { // Export course err := exporter.Export(course, outputPath) if err != nil { - t.Fatalf("Export failed: %v", err) } @@ -444,7 +439,6 @@ func TestDocxExporter_EmptyCourse(t *testing.T) { err := exporter.Export(course, outputPath) if err != nil { - t.Fatalf("Export failed: %v", err) } @@ -493,7 +487,6 @@ func TestDocxExporter_HTMLCleaning(t *testing.T) { err := exporter.Export(course, outputPath) if err != nil { - t.Fatalf("Export failed: %v", err) } @@ -516,7 +509,6 @@ func TestDocxExporter_ExistingDocxExtension(t *testing.T) { err := exporter.Export(testCourse, outputPath) if err != nil { - t.Fatalf("Export failed: %v", err) } @@ -552,7 +544,6 @@ func TestDocxExporter_CaseInsensitiveExtension(t *testing.T) { err := exporter.Export(testCourse, outputPath) if err != nil { - t.Fatalf("Export failed for case %d (%s): %v", i, testCase, err) } diff --git a/internal/exporters/factory_test.go b/internal/exporters/factory_test.go index 34bec2b..cf3eb2e 100644 --- a/internal/exporters/factory_test.go +++ b/internal/exporters/factory_test.go @@ -164,7 +164,6 @@ func TestFactory_CreateExporter_CaseInsensitive(t *testing.T) { for _, tc := range testCases { t.Run(tc.format, func(t *testing.T) { exporter, err := factory.CreateExporter(tc.format) - if err != nil { t.Fatalf("Unexpected error for format '%s': %v", tc.format, err) } diff --git a/internal/exporters/html.go b/internal/exporters/html.go index eea187f..3eaf792 100644 --- a/internal/exporters/html.go +++ b/internal/exporters/html.go @@ -9,11 +9,12 @@ import ( "os" "strings" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "github.com/kjanat/articulate-parser/internal/interfaces" "github.com/kjanat/articulate-parser/internal/models" "github.com/kjanat/articulate-parser/internal/services" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) // HTMLExporter implements the Exporter interface for HTML format. @@ -111,7 +112,7 @@ func (e *HTMLExporter) Export(course *models.Course, outputPath string) error { buf.WriteString("\n") // #nosec G306 - 0644 is appropriate for export files that should be readable by others - return os.WriteFile(outputPath, buf.Bytes(), 0644) + return os.WriteFile(outputPath, buf.Bytes(), 0o644) } // SupportedFormat returns the format name this exporter supports @@ -123,7 +124,7 @@ func (e *HTMLExporter) SupportedFormat() string { return "html" } -// getDefaultCSS returns basic CSS styling for the HTML document +// getDefaultCSS returns basic CSS styling for the HTML document. func (e *HTMLExporter) getDefaultCSS() string { return ` body { @@ -330,7 +331,7 @@ func (e *HTMLExporter) processItemToHTML(buf *bytes.Buffer, item models.Item) { } } -// processTextItem handles text content with headings and paragraphs +// processTextItem handles text content with headings and paragraphs. func (e *HTMLExporter) processTextItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n") buf.WriteString("

Text Content

\n") @@ -345,7 +346,7 @@ func (e *HTMLExporter) processTextItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n\n") } -// processListItem handles list content +// processListItem handles list content. func (e *HTMLExporter) processListItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n") buf.WriteString("

List

\n") @@ -360,7 +361,7 @@ func (e *HTMLExporter) processListItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n\n") } -// processKnowledgeCheckItem handles quiz questions and answers +// processKnowledgeCheckItem handles quiz questions and answers. func (e *HTMLExporter) processKnowledgeCheckItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n") buf.WriteString("

Knowledge Check

\n") @@ -378,7 +379,7 @@ func (e *HTMLExporter) processKnowledgeCheckItem(buf *bytes.Buffer, item models. buf.WriteString("
\n\n") } -// processMultimediaItem handles multimedia content like videos +// processMultimediaItem handles multimedia content like videos. func (e *HTMLExporter) processMultimediaItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n") buf.WriteString("

Media Content

\n") @@ -403,7 +404,7 @@ func (e *HTMLExporter) processMultimediaItem(buf *bytes.Buffer, item models.Item buf.WriteString("
\n\n") } -// processImageItem handles image content +// processImageItem handles image content. func (e *HTMLExporter) processImageItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n") buf.WriteString("

Image

\n") @@ -420,7 +421,7 @@ func (e *HTMLExporter) processImageItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n\n") } -// processInteractiveItem handles interactive content +// processInteractiveItem handles interactive content. func (e *HTMLExporter) processInteractiveItem(buf *bytes.Buffer, item models.Item) { buf.WriteString("
\n") buf.WriteString("

Interactive Content

\n") @@ -435,12 +436,12 @@ func (e *HTMLExporter) processInteractiveItem(buf *bytes.Buffer, item models.Ite buf.WriteString("
\n\n") } -// processDividerItem handles divider elements +// processDividerItem handles divider elements. func (e *HTMLExporter) processDividerItem(buf *bytes.Buffer) { buf.WriteString("
\n\n") } -// processUnknownItem handles unknown or unsupported item types +// processUnknownItem handles unknown or unsupported item types. func (e *HTMLExporter) processUnknownItem(buf *bytes.Buffer, item models.Item) { if len(item.Items) > 0 { buf.WriteString("
\n") @@ -453,7 +454,7 @@ func (e *HTMLExporter) processUnknownItem(buf *bytes.Buffer, item models.Item) { } } -// processGenericSubItem processes sub-items for unknown types +// processGenericSubItem processes sub-items for unknown types. func (e *HTMLExporter) processGenericSubItem(buf *bytes.Buffer, subItem models.SubItem) { if subItem.Title != "" { fmt.Fprintf(buf, "

%s

\n", subItem.Title) @@ -463,7 +464,7 @@ func (e *HTMLExporter) processGenericSubItem(buf *bytes.Buffer, subItem models.S } } -// processAnswers processes answer choices for quiz questions +// processAnswers processes answer choices for quiz questions. func (e *HTMLExporter) processAnswers(buf *bytes.Buffer, answers []models.Answer) { buf.WriteString("
\n") buf.WriteString("
Answers:
\n") diff --git a/internal/exporters/markdown.go b/internal/exporters/markdown.go index 02dd18c..088943b 100644 --- a/internal/exporters/markdown.go +++ b/internal/exporters/markdown.go @@ -8,11 +8,12 @@ import ( "os" "strings" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "github.com/kjanat/articulate-parser/internal/interfaces" "github.com/kjanat/articulate-parser/internal/models" "github.com/kjanat/articulate-parser/internal/services" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) // MarkdownExporter implements the Exporter interface for Markdown format. @@ -81,7 +82,7 @@ func (e *MarkdownExporter) Export(course *models.Course, outputPath string) erro } // #nosec G306 - 0644 is appropriate for export files that should be readable by others - return os.WriteFile(outputPath, buf.Bytes(), 0644) + return os.WriteFile(outputPath, buf.Bytes(), 0o644) } // SupportedFormat returns "markdown". @@ -114,7 +115,7 @@ func (e *MarkdownExporter) processItemToMarkdown(buf *bytes.Buffer, item models. } } -// processTextItem handles text content with headings and paragraphs +// processTextItem handles text content with headings and paragraphs. func (e *MarkdownExporter) processTextItem(buf *bytes.Buffer, item models.Item, headingPrefix string) { for _, subItem := range item.Items { if subItem.Heading != "" { @@ -132,7 +133,7 @@ func (e *MarkdownExporter) processTextItem(buf *bytes.Buffer, item models.Item, } } -// processListItem handles list items with bullet points +// processListItem handles list items with bullet points. func (e *MarkdownExporter) processListItem(buf *bytes.Buffer, item models.Item) { for _, subItem := range item.Items { if subItem.Paragraph != "" { @@ -145,7 +146,7 @@ func (e *MarkdownExporter) processListItem(buf *bytes.Buffer, item models.Item) buf.WriteString("\n") } -// processMultimediaItem handles multimedia content including videos and images +// processMultimediaItem handles multimedia content including videos and images. func (e *MarkdownExporter) processMultimediaItem(buf *bytes.Buffer, item models.Item, headingPrefix string) { fmt.Fprintf(buf, "%s Media Content\n\n", headingPrefix) for _, subItem := range item.Items { @@ -154,7 +155,7 @@ func (e *MarkdownExporter) processMultimediaItem(buf *bytes.Buffer, item models. buf.WriteString("\n") } -// processMediaSubItem processes individual media items (video/image) +// processMediaSubItem processes individual media items (video/image). func (e *MarkdownExporter) processMediaSubItem(buf *bytes.Buffer, subItem models.SubItem) { if subItem.Media != nil { e.processVideoMedia(buf, subItem.Media) @@ -166,7 +167,7 @@ func (e *MarkdownExporter) processMediaSubItem(buf *bytes.Buffer, subItem models } } -// processVideoMedia processes video media content +// processVideoMedia processes video media content. func (e *MarkdownExporter) processVideoMedia(buf *bytes.Buffer, media *models.Media) { if media.Video != nil { fmt.Fprintf(buf, "**Video**: %s\n", media.Video.OriginalUrl) @@ -176,14 +177,14 @@ func (e *MarkdownExporter) processVideoMedia(buf *bytes.Buffer, media *models.Me } } -// processImageMedia processes image media content +// processImageMedia processes image media content. func (e *MarkdownExporter) processImageMedia(buf *bytes.Buffer, media *models.Media) { if media.Image != nil { fmt.Fprintf(buf, "**Image**: %s\n", media.Image.OriginalUrl) } } -// processImageItem handles standalone image items +// processImageItem handles standalone image items. func (e *MarkdownExporter) processImageItem(buf *bytes.Buffer, item models.Item, headingPrefix string) { fmt.Fprintf(buf, "%s Image\n\n", headingPrefix) for _, subItem := range item.Items { @@ -198,7 +199,7 @@ func (e *MarkdownExporter) processImageItem(buf *bytes.Buffer, item models.Item, buf.WriteString("\n") } -// processKnowledgeCheckItem handles quiz questions and knowledge checks +// processKnowledgeCheckItem handles quiz questions and knowledge checks. func (e *MarkdownExporter) processKnowledgeCheckItem(buf *bytes.Buffer, item models.Item, headingPrefix string) { fmt.Fprintf(buf, "%s Knowledge Check\n\n", headingPrefix) for _, subItem := range item.Items { @@ -207,7 +208,7 @@ func (e *MarkdownExporter) processKnowledgeCheckItem(buf *bytes.Buffer, item mod buf.WriteString("\n") } -// processQuestionSubItem processes individual question items +// processQuestionSubItem processes individual question items. func (e *MarkdownExporter) processQuestionSubItem(buf *bytes.Buffer, subItem models.SubItem) { if subItem.Title != "" { title := e.htmlCleaner.CleanHTML(subItem.Title) @@ -222,7 +223,7 @@ func (e *MarkdownExporter) processQuestionSubItem(buf *bytes.Buffer, subItem mod } } -// processAnswers processes answer choices for quiz questions +// processAnswers processes answer choices for quiz questions. func (e *MarkdownExporter) processAnswers(buf *bytes.Buffer, answers []models.Answer) { buf.WriteString("**Answers**:\n") for i, answer := range answers { @@ -234,7 +235,7 @@ func (e *MarkdownExporter) processAnswers(buf *bytes.Buffer, answers []models.An } } -// processInteractiveItem handles interactive content +// processInteractiveItem handles interactive content. func (e *MarkdownExporter) processInteractiveItem(buf *bytes.Buffer, item models.Item, headingPrefix string) { fmt.Fprintf(buf, "%s Interactive Content\n\n", headingPrefix) for _, subItem := range item.Items { @@ -245,12 +246,12 @@ func (e *MarkdownExporter) processInteractiveItem(buf *bytes.Buffer, item models } } -// processDividerItem handles divider elements +// processDividerItem handles divider elements. func (e *MarkdownExporter) processDividerItem(buf *bytes.Buffer) { buf.WriteString("---\n\n") } -// processUnknownItem handles unknown or unsupported item types +// processUnknownItem handles unknown or unsupported item types. func (e *MarkdownExporter) processUnknownItem(buf *bytes.Buffer, item models.Item, headingPrefix string) { if len(item.Items) > 0 { caser := cases.Title(language.English) @@ -261,7 +262,7 @@ func (e *MarkdownExporter) processUnknownItem(buf *bytes.Buffer, item models.Ite } } -// processGenericSubItem processes sub-items for unknown types +// processGenericSubItem processes sub-items for unknown types. func (e *MarkdownExporter) processGenericSubItem(buf *bytes.Buffer, subItem models.SubItem) { if subItem.Title != "" { title := e.htmlCleaner.CleanHTML(subItem.Title) diff --git a/internal/models/models_test.go b/internal/models/models_test.go index 704efdd..4b6e812 100644 --- a/internal/models/models_test.go +++ b/internal/models/models_test.go @@ -569,7 +569,7 @@ func TestNilPointerSafety(t *testing.T) { // TestJSONTagsPresence tests that JSON tags are properly defined. func TestJSONTagsPresence(t *testing.T) { // Test that important fields have JSON tags - courseType := reflect.TypeOf(Course{}) + courseType := reflect.TypeFor[Course]() if courseType.Kind() == reflect.Struct { field, found := courseType.FieldByName("ShareID") if !found { @@ -586,7 +586,7 @@ func TestJSONTagsPresence(t *testing.T) { } // Test CourseInfo - courseInfoType := reflect.TypeOf(CourseInfo{}) + courseInfoType := reflect.TypeFor[CourseInfo]() if courseInfoType.Kind() == reflect.Struct { field, found := courseInfoType.FieldByName("NavigationMode") if !found { @@ -665,7 +665,7 @@ func BenchmarkCourse_JSONUnmarshal(b *testing.B) { } } -// compareMaps compares two interface{} values that should be maps +// compareMaps compares two any values that should be maps. func compareMaps(original, unmarshaled any) bool { origMap, origOk := original.(map[string]any) unMap, unOk := unmarshaled.(map[string]any) @@ -712,7 +712,7 @@ func compareMaps(original, unmarshaled any) bool { return true } -// compareLessons compares two Lesson structs accounting for JSON type conversion +// compareLessons compares two Lesson structs accounting for JSON type conversion. func compareLessons(original, unmarshaled Lesson) bool { // Compare all fields except Position and Items if original.ID != unmarshaled.ID || @@ -735,7 +735,7 @@ func compareLessons(original, unmarshaled Lesson) bool { return compareItems(original.Items, unmarshaled.Items) } -// compareItems compares two Item slices accounting for JSON type conversion +// compareItems compares two Item slices accounting for JSON type conversion. func compareItems(original, unmarshaled []Item) bool { if len(original) != len(unmarshaled) { return false @@ -749,7 +749,7 @@ func compareItems(original, unmarshaled []Item) bool { return true } -// compareItem compares two Item structs accounting for JSON type conversion +// compareItem compares two Item structs accounting for JSON type conversion. func compareItem(original, unmarshaled Item) bool { // Compare basic fields if original.ID != unmarshaled.ID || diff --git a/internal/services/parser.go b/internal/services/parser.go index cd9e57d..0c2cf32 100644 --- a/internal/services/parser.go +++ b/internal/services/parser.go @@ -61,7 +61,7 @@ func (p *ArticulateParser) FetchCourse(ctx context.Context, uri string) (*models apiURL := p.buildAPIURL(shareID) - req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, http.NoBody) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } diff --git a/internal/services/parser_bench_test.go b/internal/services/parser_bench_test.go index 74b150b..10552ac 100644 --- a/internal/services/parser_bench_test.go +++ b/internal/services/parser_bench_test.go @@ -130,7 +130,7 @@ func BenchmarkArticulateParser_LoadCourseFromFile(b *testing.B) { b.Fatalf("Failed to marshal: %v", err) } - if err := os.WriteFile(tempFile, data, 0644); err != nil { + if err := os.WriteFile(tempFile, data, 0o644); err != nil { b.Fatalf("Failed to write file: %v", err) } @@ -177,7 +177,7 @@ func BenchmarkArticulateParser_LoadCourseFromFile_Large(b *testing.B) { b.Fatalf("Failed to marshal: %v", err) } - if err := os.WriteFile(tempFile, data, 0644); err != nil { + if err := os.WriteFile(tempFile, data, 0o644); err != nil { b.Fatalf("Failed to write file: %v", err) } diff --git a/internal/services/parser_context_test.go b/internal/services/parser_context_test.go index 2d312b6..bebc7d6 100644 --- a/internal/services/parser_context_test.go +++ b/internal/services/parser_context_test.go @@ -144,7 +144,7 @@ func TestArticulateParser_FetchCourse_ContextDeadline(t *testing.T) { } // TestArticulateParser_FetchCourse_ContextSuccess tests that FetchCourse -// succeeds when context is not cancelled. +// succeeds when context is not canceled. func TestArticulateParser_FetchCourse_ContextSuccess(t *testing.T) { testCourse := &models.Course{ ShareID: "test-id", @@ -173,7 +173,6 @@ func TestArticulateParser_FetchCourse_ContextSuccess(t *testing.T) { defer cancel() course, err := parser.FetchCourse(ctx, "https://rise.articulate.com/share/test-id") - if err != nil { t.Fatalf("Expected no error, got: %v", err) } diff --git a/internal/services/parser_test.go b/internal/services/parser_test.go index 4a60ad3..a7ea917 100644 --- a/internal/services/parser_test.go +++ b/internal/services/parser_test.go @@ -209,7 +209,7 @@ func TestArticulateParser_LoadCourseFromFile(t *testing.T) { t.Fatalf("Failed to marshal test course: %v", err) } - if err := os.WriteFile(tempFile, data, 0644); err != nil { + if err := os.WriteFile(tempFile, data, 0o644); err != nil { t.Fatalf("Failed to write test file: %v", err) } @@ -268,7 +268,7 @@ func TestArticulateParser_LoadCourseFromFile_InvalidJSON(t *testing.T) { tempDir := t.TempDir() tempFile := filepath.Join(tempDir, "invalid.json") - if err := os.WriteFile(tempFile, []byte("invalid json content"), 0644); err != nil { + if err := os.WriteFile(tempFile, []byte("invalid json content"), 0o644); err != nil { t.Fatalf("Failed to write test file: %v", err) } diff --git a/internal/version/version.go b/internal/version/version.go index e3e780d..b701bdd 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -14,7 +14,7 @@ var ( // - Structured logging with slog // - Configuration via environment variables // - Context-aware HTTP requests - // - Comprehensive benchmarks and examples + // - Comprehensive benchmarks and examples. Version = "1.0.0" // BuildTime is the time the binary was built.