diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec26478..01787a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -467,7 +467,7 @@ jobs: # Process a local file (mount current directory) docker run --rm -v $(pwd):/workspace ghcr.io/${{ github.repository }}:latest /workspace/input.json markdown /workspace/output.md ``` - + EOF # Security and quality analysis workflows diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ef94164..5074a81 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,4 +1,3 @@ -// Package config_test provides tests for the config package. package config import ( diff --git a/internal/exporters/bench_test.go b/internal/exporters/bench_test.go index 5f91aab..3a485b6 100644 --- a/internal/exporters/bench_test.go +++ b/internal/exporters/bench_test.go @@ -1,4 +1,3 @@ -// Package exporters_test provides benchmarks for all exporters. package exporters import ( diff --git a/internal/exporters/docx.go b/internal/exporters/docx.go index 56db39c..56b9f34 100644 --- a/internal/exporters/docx.go +++ b/internal/exporters/docx.go @@ -69,7 +69,7 @@ func (e *DocxExporter) Export(course *models.Course, outputPath string) error { // Ensure output directory exists and add .docx extension if !strings.HasSuffix(strings.ToLower(outputPath), ".docx") { - outputPath = outputPath + ".docx" + outputPath += ".docx" } // Create the file @@ -198,5 +198,5 @@ func (e *DocxExporter) exportSubItem(doc *docx.Docx, subItem *models.SubItem) { // Returns: // - A string representing the supported format ("docx") func (e *DocxExporter) SupportedFormat() string { - return "docx" + return FormatDocx } diff --git a/internal/exporters/docx_test.go b/internal/exporters/docx_test.go index 374fcbb..9488d6b 100644 --- a/internal/exporters/docx_test.go +++ b/internal/exporters/docx_test.go @@ -1,4 +1,3 @@ -// Package exporters_test provides tests for the docx exporter. package exporters import ( @@ -331,7 +330,7 @@ func TestDocxExporter_ComplexCourse(t *testing.T) { Caption: "

Watch this introductory video

", Media: &models.Media{ Video: &models.VideoMedia{ - OriginalUrl: "https://example.com/intro.mp4", + OriginalURL: "https://example.com/intro.mp4", Duration: 300, }, }, @@ -359,7 +358,7 @@ func TestDocxExporter_ComplexCourse(t *testing.T) { Caption: "

Course overview diagram

", Media: &models.Media{ Image: &models.ImageMedia{ - OriginalUrl: "https://example.com/overview.png", + OriginalURL: "https://example.com/overview.png", }, }, }, diff --git a/internal/exporters/factory.go b/internal/exporters/factory.go index 82f4560..bda72b0 100644 --- a/internal/exporters/factory.go +++ b/internal/exporters/factory.go @@ -1,5 +1,3 @@ -// Package exporters provides implementations of the Exporter interface -// for converting Articulate Rise courses into various file formats. package exporters import ( @@ -10,6 +8,13 @@ import ( "github.com/kjanat/articulate-parser/internal/services" ) +// Format constants for supported export formats. +const ( + FormatMarkdown = "markdown" + FormatDocx = "docx" + FormatHTML = "html" +) + // Factory implements the ExporterFactory interface. // It creates appropriate exporter instances based on the requested format. type Factory struct { @@ -36,11 +41,11 @@ func NewFactory(htmlCleaner *services.HTMLCleaner) interfaces.ExporterFactory { // Format strings are case-insensitive (e.g., "markdown", "DOCX"). func (f *Factory) CreateExporter(format string) (interfaces.Exporter, error) { switch strings.ToLower(format) { - case "markdown", "md": + case FormatMarkdown, "md": return NewMarkdownExporter(f.htmlCleaner), nil - case "docx", "word": + case FormatDocx, "word": return NewDocxExporter(f.htmlCleaner), nil - case "html", "htm": + case FormatHTML, "htm": return NewHTMLExporter(f.htmlCleaner), nil default: return nil, fmt.Errorf("unsupported export format: %s", format) @@ -50,5 +55,5 @@ func (f *Factory) CreateExporter(format string) (interfaces.Exporter, error) { // SupportedFormats returns a list of all supported export formats, // including both primary format names and their aliases. func (f *Factory) SupportedFormats() []string { - return []string{"markdown", "md", "docx", "word", "html", "htm"} + return []string{FormatMarkdown, "md", FormatDocx, "word", FormatHTML, "htm"} } diff --git a/internal/exporters/factory_test.go b/internal/exporters/factory_test.go index cf3eb2e..8379e14 100644 --- a/internal/exporters/factory_test.go +++ b/internal/exporters/factory_test.go @@ -1,4 +1,3 @@ -// Package exporters_test provides tests for the exporter factory. package exporters import ( diff --git a/internal/exporters/html.go b/internal/exporters/html.go index 3eaf792..d170320 100644 --- a/internal/exporters/html.go +++ b/internal/exporters/html.go @@ -1,5 +1,3 @@ -// Package exporters provides implementations of the Exporter interface -// for converting Articulate Rise courses into various file formats. package exporters import ( @@ -112,7 +110,10 @@ 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(), 0o644) + if err := os.WriteFile(outputPath, buf.Bytes(), 0o644); err != nil { + return fmt.Errorf("failed to write HTML file: %w", err) + } + return nil } // SupportedFormat returns the format name this exporter supports @@ -121,7 +122,7 @@ func (e *HTMLExporter) Export(course *models.Course, outputPath string) error { // Returns: // - A string representing the supported format ("html") func (e *HTMLExporter) SupportedFormat() string { - return "html" + return FormatHTML } // getDefaultCSS returns basic CSS styling for the HTML document. @@ -390,7 +391,7 @@ func (e *HTMLExporter) processMultimediaItem(buf *bytes.Buffer, item models.Item if subItem.Media != nil { if subItem.Media.Video != nil { buf.WriteString("
\n") - fmt.Fprintf(buf, "

Video: %s

\n", html.EscapeString(subItem.Media.Video.OriginalUrl)) + fmt.Fprintf(buf, "

Video: %s

\n", html.EscapeString(subItem.Media.Video.OriginalURL)) if subItem.Media.Video.Duration > 0 { fmt.Fprintf(buf, "

Duration: %d seconds

\n", subItem.Media.Video.Duration) } @@ -411,7 +412,7 @@ func (e *HTMLExporter) processImageItem(buf *bytes.Buffer, item models.Item) { for _, subItem := range item.Items { if subItem.Media != nil && subItem.Media.Image != nil { buf.WriteString("
\n") - fmt.Fprintf(buf, "

Image: %s

\n", html.EscapeString(subItem.Media.Image.OriginalUrl)) + fmt.Fprintf(buf, "

Image: %s

\n", html.EscapeString(subItem.Media.Image.OriginalURL)) buf.WriteString("
\n") } if subItem.Caption != "" { diff --git a/internal/exporters/html_test.go b/internal/exporters/html_test.go index d3de0de..01329aa 100644 --- a/internal/exporters/html_test.go +++ b/internal/exporters/html_test.go @@ -1,4 +1,3 @@ -// Package exporters_test provides tests for the html exporter. package exporters import ( @@ -270,7 +269,7 @@ func TestHTMLExporter_ProcessMultimediaItem(t *testing.T) { Title: "

Video Title

", Media: &models.Media{ Video: &models.VideoMedia{ - OriginalUrl: "https://example.com/video.mp4", + OriginalURL: "https://example.com/video.mp4", Duration: 120, }, }, @@ -315,7 +314,7 @@ func TestHTMLExporter_ProcessImageItem(t *testing.T) { { Media: &models.Media{ Image: &models.ImageMedia{ - OriginalUrl: "https://example.com/image.png", + OriginalURL: "https://example.com/image.png", }, }, Caption: "

Image caption

", diff --git a/internal/exporters/markdown.go b/internal/exporters/markdown.go index 088943b..b2cce40 100644 --- a/internal/exporters/markdown.go +++ b/internal/exporters/markdown.go @@ -1,5 +1,3 @@ -// Package exporters provides implementations of the Exporter interface -// for converting Articulate Rise courses into various file formats. package exporters import ( @@ -82,12 +80,15 @@ 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(), 0o644) + if err := os.WriteFile(outputPath, buf.Bytes(), 0o644); err != nil { + return fmt.Errorf("failed to write markdown file: %w", err) + } + return nil } // SupportedFormat returns "markdown". func (e *MarkdownExporter) SupportedFormat() string { - return "markdown" + return FormatMarkdown } // processItemToMarkdown converts a course item into Markdown format. @@ -170,7 +171,7 @@ func (e *MarkdownExporter) processMediaSubItem(buf *bytes.Buffer, subItem models // 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) + fmt.Fprintf(buf, "**Video**: %s\n", media.Video.OriginalURL) if media.Video.Duration > 0 { fmt.Fprintf(buf, "**Duration**: %d seconds\n", media.Video.Duration) } @@ -180,7 +181,7 @@ func (e *MarkdownExporter) processVideoMedia(buf *bytes.Buffer, media *models.Me // 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) + fmt.Fprintf(buf, "**Image**: %s\n", media.Image.OriginalURL) } } @@ -189,7 +190,7 @@ func (e *MarkdownExporter) processImageItem(buf *bytes.Buffer, item models.Item, fmt.Fprintf(buf, "%s Image\n\n", headingPrefix) for _, subItem := range item.Items { if subItem.Media != nil && subItem.Media.Image != nil { - fmt.Fprintf(buf, "**Image**: %s\n", subItem.Media.Image.OriginalUrl) + fmt.Fprintf(buf, "**Image**: %s\n", subItem.Media.Image.OriginalURL) } if subItem.Caption != "" { caption := e.htmlCleaner.CleanHTML(subItem.Caption) diff --git a/internal/exporters/markdown_test.go b/internal/exporters/markdown_test.go index 55ff485..84a6fdd 100644 --- a/internal/exporters/markdown_test.go +++ b/internal/exporters/markdown_test.go @@ -1,4 +1,3 @@ -// Package exporters_test provides tests for the markdown exporter. package exporters import ( @@ -188,7 +187,7 @@ func TestMarkdownExporter_ProcessMultimediaItem(t *testing.T) { { Media: &models.Media{ Video: &models.VideoMedia{ - OriginalUrl: "https://example.com/video.mp4", + OriginalURL: "https://example.com/video.mp4", Duration: 120, }, }, @@ -227,7 +226,7 @@ func TestMarkdownExporter_ProcessImageItem(t *testing.T) { { Media: &models.Media{ Image: &models.ImageMedia{ - OriginalUrl: "https://example.com/image.jpg", + OriginalURL: "https://example.com/image.jpg", }, }, Caption: "

Image caption

", @@ -372,7 +371,7 @@ func TestMarkdownExporter_ProcessVideoMedia(t *testing.T) { var buf bytes.Buffer media := &models.Media{ Video: &models.VideoMedia{ - OriginalUrl: "https://example.com/video.mp4", + OriginalURL: "https://example.com/video.mp4", Duration: 300, }, } @@ -397,7 +396,7 @@ func TestMarkdownExporter_ProcessImageMedia(t *testing.T) { var buf bytes.Buffer media := &models.Media{ Image: &models.ImageMedia{ - OriginalUrl: "https://example.com/image.jpg", + OriginalURL: "https://example.com/image.jpg", }, } diff --git a/internal/interfaces/exporter.go b/internal/interfaces/exporter.go index 671e48b..0c6505c 100644 --- a/internal/interfaces/exporter.go +++ b/internal/interfaces/exporter.go @@ -1,5 +1,3 @@ -// Package interfaces provides the core contracts for the articulate-parser application. -// It defines interfaces for parsing and exporting Articulate Rise courses. package interfaces import "github.com/kjanat/articulate-parser/internal/models" diff --git a/internal/interfaces/logger.go b/internal/interfaces/logger.go index 7ff33d5..2d30701 100644 --- a/internal/interfaces/logger.go +++ b/internal/interfaces/logger.go @@ -1,5 +1,3 @@ -// Package interfaces provides the core contracts for the articulate-parser application. -// It defines interfaces for parsing and exporting Articulate Rise courses. package interfaces import "context" diff --git a/internal/models/lesson.go b/internal/models/lesson.go index 26a9aa5..95866ee 100644 --- a/internal/models/lesson.go +++ b/internal/models/lesson.go @@ -1,5 +1,3 @@ -// Package models defines the data structures representing Articulate Rise courses. -// These structures closely match the JSON format used by Articulate Rise. package models // Lesson represents a single lesson or section within an Articulate Rise course. diff --git a/internal/models/media.go b/internal/models/media.go index c456832..f2a303a 100644 --- a/internal/models/media.go +++ b/internal/models/media.go @@ -1,5 +1,3 @@ -// Package models defines the data structures representing Articulate Rise courses. -// These structures closely match the JSON format used by Articulate Rise. package models // Media represents a media element that can be either an image or a video. @@ -23,8 +21,8 @@ type ImageMedia struct { Height int `json:"height,omitempty"` // CrushedKey is the identifier for a compressed version of the image CrushedKey string `json:"crushedKey,omitempty"` - // OriginalUrl is the URL to the full-resolution image - OriginalUrl string `json:"originalUrl"` + // OriginalURL is the URL to the full-resolution image + OriginalURL string `json:"originalUrl"` // UseCrushedKey indicates whether to use the compressed version UseCrushedKey bool `json:"useCrushedKey,omitempty"` } @@ -45,6 +43,6 @@ type VideoMedia struct { InputKey string `json:"inputKey,omitempty"` // Thumbnail is the URL to a smaller preview image Thumbnail string `json:"thumbnail,omitempty"` - // OriginalUrl is the URL to the source video file - OriginalUrl string `json:"originalUrl"` + // OriginalURL is the URL to the source video file + OriginalURL string `json:"originalUrl"` } diff --git a/internal/models/models_test.go b/internal/models/models_test.go index 4b6e812..f830c24 100644 --- a/internal/models/models_test.go +++ b/internal/models/models_test.go @@ -1,4 +1,3 @@ -// Package models_test provides tests for the data models. package models import ( @@ -98,7 +97,7 @@ func TestCourseInfo_JSONMarshalUnmarshal(t *testing.T) { Type: "jpg", Width: 800, Height: 600, - OriginalUrl: "https://example.com/image.jpg", + OriginalURL: "https://example.com/image.jpg", }, }, } @@ -149,7 +148,7 @@ func TestLesson_JSONMarshalUnmarshal(t *testing.T) { URL: "https://example.com/video.mp4", Type: "mp4", Duration: 120, - OriginalUrl: "https://example.com/video.mp4", + OriginalURL: "https://example.com/video.mp4", }, }, }, @@ -244,7 +243,7 @@ func TestSubItem_JSONMarshalUnmarshal(t *testing.T) { Type: "png", Width: 400, Height: 300, - OriginalUrl: "https://example.com/subitem.png", + OriginalURL: "https://example.com/subitem.png", CrushedKey: "crushed-123", UseCrushedKey: true, }, @@ -305,7 +304,7 @@ func TestMedia_JSONMarshalUnmarshal(t *testing.T) { Type: "jpeg", Width: 1200, Height: 800, - OriginalUrl: "https://example.com/media.jpg", + OriginalURL: "https://example.com/media.jpg", CrushedKey: "crushed-media", UseCrushedKey: false, }, @@ -336,7 +335,7 @@ func TestMedia_JSONMarshalUnmarshal(t *testing.T) { Poster: "https://example.com/poster.jpg", Thumbnail: "https://example.com/thumb.jpg", InputKey: "input-123", - OriginalUrl: "https://example.com/original.mp4", + OriginalURL: "https://example.com/original.mp4", }, } @@ -363,7 +362,7 @@ func TestImageMedia_JSONMarshalUnmarshal(t *testing.T) { Type: "gif", Width: 640, Height: 480, - OriginalUrl: "https://example.com/image.gif", + OriginalURL: "https://example.com/image.gif", CrushedKey: "crushed-gif", UseCrushedKey: true, } @@ -397,7 +396,7 @@ func TestVideoMedia_JSONMarshalUnmarshal(t *testing.T) { Poster: "https://example.com/poster.jpg", Thumbnail: "https://example.com/thumbnail.jpg", InputKey: "upload-456", - OriginalUrl: "https://example.com/original.webm", + OriginalURL: "https://example.com/original.webm", } // Marshal to JSON diff --git a/internal/services/app_test.go b/internal/services/app_test.go index 4cac724..ac5215e 100644 --- a/internal/services/app_test.go +++ b/internal/services/app_test.go @@ -1,4 +1,3 @@ -// Package services_test provides tests for the services package. package services import ( @@ -217,10 +216,8 @@ func TestApp_ProcessCourseFromFile(t *testing.T) { if !contains(err.Error(), tt.expectedError) { t.Errorf("Expected error containing '%s', got '%s'", tt.expectedError, err.Error()) } - } else { - if err != nil { - t.Errorf("Expected no error, got: %v", err) - } + } else if err != nil { + t.Errorf("Expected no error, got: %v", err) } }) } @@ -298,10 +295,8 @@ func TestApp_ProcessCourseFromURI(t *testing.T) { if !contains(err.Error(), tt.expectedError) { t.Errorf("Expected error containing '%s', got '%s'", tt.expectedError, err.Error()) } - } else { - if err != nil { - t.Errorf("Expected no error, got: %v", err) - } + } else if err != nil { + t.Errorf("Expected no error, got: %v", err) } }) } @@ -335,7 +330,7 @@ func TestApp_SupportedFormats(t *testing.T) { // contains checks if a string contains a substring. func contains(s, substr string) bool { return len(s) >= len(substr) && - (len(substr) == 0 || + (substr == "" || s == substr || (len(s) > len(substr) && (s[:len(substr)] == substr || diff --git a/internal/services/html_cleaner.go b/internal/services/html_cleaner.go index 63817f6..5c28c16 100644 --- a/internal/services/html_cleaner.go +++ b/internal/services/html_cleaner.go @@ -1,5 +1,3 @@ -// Package services provides the core functionality for the articulate-parser application. -// It implements the interfaces defined in the interfaces package. package services import ( diff --git a/internal/services/html_cleaner_test.go b/internal/services/html_cleaner_test.go index 0c6836a..d696148 100644 --- a/internal/services/html_cleaner_test.go +++ b/internal/services/html_cleaner_test.go @@ -1,4 +1,3 @@ -// Package services_test provides tests for the HTML cleaner service. package services import ( diff --git a/internal/services/logger.go b/internal/services/logger.go index e352541..6f261b8 100644 --- a/internal/services/logger.go +++ b/internal/services/logger.go @@ -1,4 +1,3 @@ -// Package services provides the core functionality for the articulate-parser application. package services import ( diff --git a/internal/services/logger_bench_test.go b/internal/services/logger_bench_test.go index dd0803f..31dae17 100644 --- a/internal/services/logger_bench_test.go +++ b/internal/services/logger_bench_test.go @@ -1,4 +1,3 @@ -// Package services_test provides benchmarks for the logger implementations. package services import ( diff --git a/internal/services/parser.go b/internal/services/parser.go index 0c2cf32..a0fa2bc 100644 --- a/internal/services/parser.go +++ b/internal/services/parser.go @@ -1,5 +1,3 @@ -// Package services provides the core functionality for the articulate-parser application. -// It implements the interfaces defined in the interfaces package. package services import ( diff --git a/internal/services/parser_bench_test.go b/internal/services/parser_bench_test.go index 10552ac..74decec 100644 --- a/internal/services/parser_bench_test.go +++ b/internal/services/parser_bench_test.go @@ -1,4 +1,3 @@ -// Package services_test provides benchmarks for the parser service. package services import ( diff --git a/internal/services/parser_context_test.go b/internal/services/parser_context_test.go index bebc7d6..5964bd7 100644 --- a/internal/services/parser_context_test.go +++ b/internal/services/parser_context_test.go @@ -1,4 +1,3 @@ -// Package services_test provides context-aware tests for the parser service. package services import ( diff --git a/internal/services/parser_test.go b/internal/services/parser_test.go index a7ea917..a1c4576 100644 --- a/internal/services/parser_test.go +++ b/internal/services/parser_test.go @@ -1,4 +1,3 @@ -// Package services_test provides tests for the parser service. package services import ( diff --git a/main_test.go b/main_test.go index b417ee4..b96bdcd 100644 --- a/main_test.go +++ b/main_test.go @@ -1,4 +1,3 @@ -// Package main_test provides tests for the main package utility functions. package main import (