mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-08-06 00:04:07 +02:00
fix(lint): resolve golangci-lint failures and modernize string handling
CI's golangci-lint (v2.12.2) job was failing with 14 issues, which blocked
the dependent test job. This addresses all of them:
- goconst: extract repeated string literals into constants
- format aliases ("md", "word", "htm") in the exporter factory
- "section" lesson type shared by markdown and HTML exporters
- default Articulate Rise base URL and host in the parser
- reuse existing itemType* constants in the markdown switch
- staticcheck (QF1012): replace buf.WriteString(fmt.Sprintf(...)) with
fmt.Fprintf(...) in the markdown exporter
Also drop the hardcoded `go: "1.24"` from .golangci.yml so the target Go
version is autodetected from go.mod.
This commit is contained in:
+1
-2
@@ -10,8 +10,7 @@ run:
|
|||||||
|
|
||||||
# Skip directories (not allowed in config v2, will use issues exclude instead)
|
# Skip directories (not allowed in config v2, will use issues exclude instead)
|
||||||
|
|
||||||
# Go version
|
# Go version is autodetected from go.mod
|
||||||
go: "1.24"
|
|
||||||
|
|
||||||
# Include test files
|
# Include test files
|
||||||
tests: true
|
tests: true
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ const (
|
|||||||
FormatMarkdown = "markdown"
|
FormatMarkdown = "markdown"
|
||||||
FormatDocx = "docx"
|
FormatDocx = "docx"
|
||||||
FormatHTML = "html"
|
FormatHTML = "html"
|
||||||
|
|
||||||
|
// Format aliases accepted by CreateExporter.
|
||||||
|
formatAliasMarkdown = "md"
|
||||||
|
formatAliasDocx = "word"
|
||||||
|
formatAliasHTML = "htm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Factory implements the ExporterFactory interface.
|
// Factory implements the ExporterFactory interface.
|
||||||
@@ -41,11 +46,11 @@ func NewFactory(htmlCleaner *services.HTMLCleaner) interfaces.ExporterFactory {
|
|||||||
// Format strings are case-insensitive (e.g., "markdown", "DOCX").
|
// Format strings are case-insensitive (e.g., "markdown", "DOCX").
|
||||||
func (f *Factory) CreateExporter(format string) (interfaces.Exporter, error) {
|
func (f *Factory) CreateExporter(format string) (interfaces.Exporter, error) {
|
||||||
switch strings.ToLower(format) {
|
switch strings.ToLower(format) {
|
||||||
case FormatMarkdown, "md":
|
case FormatMarkdown, formatAliasMarkdown:
|
||||||
return NewMarkdownExporter(f.htmlCleaner), nil
|
return NewMarkdownExporter(f.htmlCleaner), nil
|
||||||
case FormatDocx, "word":
|
case FormatDocx, formatAliasDocx:
|
||||||
return NewDocxExporter(f.htmlCleaner), nil
|
return NewDocxExporter(f.htmlCleaner), nil
|
||||||
case FormatHTML, "htm":
|
case FormatHTML, formatAliasHTML:
|
||||||
return NewHTMLExporter(f.htmlCleaner), nil
|
return NewHTMLExporter(f.htmlCleaner), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported export format: %s", format)
|
return nil, fmt.Errorf("unsupported export format: %s", format)
|
||||||
@@ -55,5 +60,9 @@ func (f *Factory) CreateExporter(format string) (interfaces.Exporter, error) {
|
|||||||
// SupportedFormats returns a list of all supported export formats,
|
// SupportedFormats returns a list of all supported export formats,
|
||||||
// including both primary format names and their aliases.
|
// including both primary format names and their aliases.
|
||||||
func (f *Factory) SupportedFormats() []string {
|
func (f *Factory) SupportedFormats() []string {
|
||||||
return []string{FormatMarkdown, "md", FormatDocx, "word", FormatHTML, "htm"}
|
return []string{
|
||||||
|
FormatMarkdown, formatAliasMarkdown,
|
||||||
|
FormatDocx, formatAliasDocx,
|
||||||
|
FormatHTML, formatAliasHTML,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ const (
|
|||||||
itemTypeDivider = "divider"
|
itemTypeDivider = "divider"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// lessonTypeSection identifies a lesson that acts as a section header.
|
||||||
|
const lessonTypeSection = "section"
|
||||||
|
|
||||||
// templateData represents the data structure passed to the HTML template.
|
// templateData represents the data structure passed to the HTML template.
|
||||||
type templateData struct {
|
type templateData struct {
|
||||||
Course models.CourseInfo
|
Course models.CourseInfo
|
||||||
@@ -74,7 +77,7 @@ func prepareTemplateData(course *models.Course, htmlCleaner *services.HTMLCleane
|
|||||||
Description: lesson.Description,
|
Description: lesson.Description,
|
||||||
}
|
}
|
||||||
|
|
||||||
if lesson.Type != "section" {
|
if lesson.Type != lessonTypeSection {
|
||||||
lessonCounter++
|
lessonCounter++
|
||||||
section.Number = lessonCounter
|
section.Number = lessonCounter
|
||||||
section.Items = prepareItems(lesson.Items, htmlCleaner)
|
section.Items = prepareItems(lesson.Items, htmlCleaner)
|
||||||
|
|||||||
@@ -40,35 +40,35 @@ func (e *MarkdownExporter) Export(course *models.Course, outputPath string) erro
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
// Write course header
|
// Write course header
|
||||||
buf.WriteString(fmt.Sprintf("# %s\n\n", course.Course.Title))
|
fmt.Fprintf(&buf, "# %s\n\n", course.Course.Title)
|
||||||
|
|
||||||
if course.Course.Description != "" {
|
if course.Course.Description != "" {
|
||||||
buf.WriteString(fmt.Sprintf("%s\n\n", e.htmlCleaner.CleanHTML(course.Course.Description)))
|
fmt.Fprintf(&buf, "%s\n\n", e.htmlCleaner.CleanHTML(course.Course.Description))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add metadata
|
// Add metadata
|
||||||
buf.WriteString("## Course Information\n\n")
|
buf.WriteString("## Course Information\n\n")
|
||||||
buf.WriteString(fmt.Sprintf("- **Course ID**: %s\n", course.Course.ID))
|
fmt.Fprintf(&buf, "- **Course ID**: %s\n", course.Course.ID)
|
||||||
buf.WriteString(fmt.Sprintf("- **Share ID**: %s\n", course.ShareID))
|
fmt.Fprintf(&buf, "- **Share ID**: %s\n", course.ShareID)
|
||||||
buf.WriteString(fmt.Sprintf("- **Navigation Mode**: %s\n", course.Course.NavigationMode))
|
fmt.Fprintf(&buf, "- **Navigation Mode**: %s\n", course.Course.NavigationMode)
|
||||||
if course.Course.ExportSettings != nil {
|
if course.Course.ExportSettings != nil {
|
||||||
buf.WriteString(fmt.Sprintf("- **Export Format**: %s\n", course.Course.ExportSettings.Format))
|
fmt.Fprintf(&buf, "- **Export Format**: %s\n", course.Course.ExportSettings.Format)
|
||||||
}
|
}
|
||||||
buf.WriteString("\n---\n\n")
|
buf.WriteString("\n---\n\n")
|
||||||
|
|
||||||
// Process lessons
|
// Process lessons
|
||||||
lessonCounter := 0
|
lessonCounter := 0
|
||||||
for _, lesson := range course.Course.Lessons {
|
for _, lesson := range course.Course.Lessons {
|
||||||
if lesson.Type == "section" {
|
if lesson.Type == lessonTypeSection {
|
||||||
buf.WriteString(fmt.Sprintf("# %s\n\n", lesson.Title))
|
fmt.Fprintf(&buf, "# %s\n\n", lesson.Title)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
lessonCounter++
|
lessonCounter++
|
||||||
buf.WriteString(fmt.Sprintf("## Lesson %d: %s\n\n", lessonCounter, lesson.Title))
|
fmt.Fprintf(&buf, "## Lesson %d: %s\n\n", lessonCounter, lesson.Title)
|
||||||
|
|
||||||
if lesson.Description != "" {
|
if lesson.Description != "" {
|
||||||
buf.WriteString(fmt.Sprintf("%s\n\n", e.htmlCleaner.CleanHTML(lesson.Description)))
|
fmt.Fprintf(&buf, "%s\n\n", e.htmlCleaner.CleanHTML(lesson.Description))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process lesson items
|
// Process lesson items
|
||||||
@@ -100,19 +100,19 @@ func (e *MarkdownExporter) processItemToMarkdown(buf *bytes.Buffer, item models.
|
|||||||
itemType := strings.ToLower(item.Type)
|
itemType := strings.ToLower(item.Type)
|
||||||
|
|
||||||
switch itemType {
|
switch itemType {
|
||||||
case "text":
|
case itemTypeText:
|
||||||
e.processTextItem(buf, item, headingPrefix)
|
e.processTextItem(buf, item, headingPrefix)
|
||||||
case "list":
|
case itemTypeList:
|
||||||
e.processListItem(buf, item)
|
e.processListItem(buf, item)
|
||||||
case "multimedia":
|
case itemTypeMultimedia:
|
||||||
e.processMultimediaItem(buf, item, headingPrefix)
|
e.processMultimediaItem(buf, item, headingPrefix)
|
||||||
case "image":
|
case itemTypeImage:
|
||||||
e.processImageItem(buf, item, headingPrefix)
|
e.processImageItem(buf, item, headingPrefix)
|
||||||
case "knowledgecheck":
|
case itemTypeKnowledgeCheck:
|
||||||
e.processKnowledgeCheckItem(buf, item, headingPrefix)
|
e.processKnowledgeCheckItem(buf, item, headingPrefix)
|
||||||
case "interactive":
|
case itemTypeInteractive:
|
||||||
e.processInteractiveItem(buf, item, headingPrefix)
|
e.processInteractiveItem(buf, item, headingPrefix)
|
||||||
case "divider":
|
case itemTypeDivider:
|
||||||
e.processDividerItem(buf)
|
e.processDividerItem(buf)
|
||||||
default:
|
default:
|
||||||
e.processUnknownItem(buf, item, headingPrefix)
|
e.processUnknownItem(buf, item, headingPrefix)
|
||||||
|
|||||||
@@ -15,6 +15,14 @@ import (
|
|||||||
"github.com/kjanat/articulate-parser/internal/models"
|
"github.com/kjanat/articulate-parser/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Default endpoint configuration for the Articulate Rise API.
|
||||||
|
const (
|
||||||
|
// Root URL for the Articulate Rise API.
|
||||||
|
defaultBaseURL = "https://rise.articulate.com"
|
||||||
|
// Expected host for Articulate Rise share URLs.
|
||||||
|
riseHost = "rise.articulate.com"
|
||||||
|
)
|
||||||
|
|
||||||
// shareIDRegex is compiled once at package init for extracting share IDs from URIs.
|
// shareIDRegex is compiled once at package init for extracting share IDs from URIs.
|
||||||
var shareIDRegex = regexp.MustCompile(`/share/([a-zA-Z0-9_-]+)`)
|
var shareIDRegex = regexp.MustCompile(`/share/([a-zA-Z0-9_-]+)`)
|
||||||
|
|
||||||
@@ -37,7 +45,7 @@ func NewArticulateParser(logger interfaces.Logger, baseURL string, timeout time.
|
|||||||
logger = NewNoOpLogger()
|
logger = NewNoOpLogger()
|
||||||
}
|
}
|
||||||
if baseURL == "" {
|
if baseURL == "" {
|
||||||
baseURL = "https://rise.articulate.com"
|
baseURL = defaultBaseURL
|
||||||
}
|
}
|
||||||
if timeout == 0 {
|
if timeout == 0 {
|
||||||
timeout = 30 * time.Second
|
timeout = 30 * time.Second
|
||||||
@@ -132,7 +140,7 @@ func (p *ArticulateParser) extractShareID(uri string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate that it's an Articulate Rise domain
|
// Validate that it's an Articulate Rise domain
|
||||||
if parsedURL.Host != "rise.articulate.com" {
|
if parsedURL.Host != riseHost {
|
||||||
return "", fmt.Errorf("invalid domain for Articulate Rise URI: %s", parsedURL.Host)
|
return "", fmt.Errorf("invalid domain for Articulate Rise URI: %s", parsedURL.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user