mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-08-05 20:04:06 +02:00
144125d355
* 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.
* ci: drop test matrix, run a single Go version from go.mod
The test job ran a 1.24.x/1.25.x matrix, but go.mod requires go 1.25.0, so the
1.24 entry just auto-downloaded the 1.25 toolchain and tested the same thing
twice. Replace the matrix with a single job that sources its Go version from
go.mod via go-version-file, and reference the resolved version through the
setup-go step output in summaries, artifact names, and Codecov flags.
* docs: remove emojis from README
* ci: pin modernize tool to gopls v0.21.x for Go 1.25 compatibility
The autofix workflow's `task modernize` step installed the modernize
analyzer from gopls@latest, which as of v0.22.0 requires Go 1.26. The
project targets Go 1.25 (go.mod) and CI runs with GOTOOLCHAIN=local, so
the install failed. Pin to the v0.21.x line, which supports Go >= 1.25.
* ci: keep modernize@latest, fetch its toolchain via GOTOOLCHAIN=auto
Replaces the earlier v0.21.0 pin. The modernize analyzer (gopls v0.22+)
requires Go 1.26, which the project doesn't target yet — and bumping the
module to 1.26 isn't viable because the current golangci-lint release is
built with Go 1.25 and refuses to lint a newer target. Instead, let the
modernize task fetch the toolchain it needs on demand via GOTOOLCHAIN=auto
(setup-go pins GOTOOLCHAIN=local in CI), so we stay on the latest analyzer
without touching the module's Go version.
Also bump golang.org/x/image v0.34.0 -> v0.42.0 via `go get -u ./...`.
* ci: set GOTOOLCHAIN=auto inline for the modernize task
A task-level env: entry does not override GOTOOLCHAIN when setup-go has
already exported GOTOOLCHAIN=local job-wide, so the autofix job still
failed. Set GOTOOLCHAIN=auto inline on the modernize command itself, which
reliably overrides the inherited value and lets Go fetch the toolchain the
modernize analyzer requires.
* ci: bump to Go 1.26 and address PR review feedback
The autofix failure is fixed properly by moving to Go 1.26 instead of the
GOTOOLCHAIN workaround. The official golangci-lint v2.12.2 binary is built
with go1.26.2 and lints a Go 1.26 target fine (the earlier "not ready"
claim was from a locally go-installed binary compiled with Go 1.25), and
the Dockerfiles already use golang:1.26-alpine, so this also aligns the
module with the images.
- go.mod: go 1.25.0 -> 1.26.0, drop the toolchain pin (keeps the lint
target at the go directive).
- Taskfile: revert modernize to plain modernize@latest; on Go 1.26 the
GOTOOLCHAIN dance and its comments are unnecessary.
- ci.yml: pass the resolved Go version through an env var (GO_VERSION)
instead of interpolating steps.setup-go.outputs.go-version directly into
shell scripts (script-injection hygiene); grant the dependency-review job
pull-requests: write so it can post its summary.
- parser.go: derive defaultBaseURL from riseHost instead of duplicating the
host string; drop the redundant per-const comments.
- .golangci.yml: remove the redundant go-version comment.
- regenerate internal/exporters/output.docx.
135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package exporters
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/kjanat/articulate-parser/internal/models"
|
|
"github.com/kjanat/articulate-parser/internal/services"
|
|
)
|
|
|
|
// Item type constants.
|
|
const (
|
|
itemTypeText = "text"
|
|
itemTypeList = "list"
|
|
itemTypeKnowledgeCheck = "knowledgecheck"
|
|
itemTypeMultimedia = "multimedia"
|
|
itemTypeImage = "image"
|
|
itemTypeInteractive = "interactive"
|
|
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.
|
|
type templateData struct {
|
|
Course models.CourseInfo
|
|
ShareID string
|
|
Sections []templateSection
|
|
CSS string
|
|
}
|
|
|
|
// templateSection represents a course section or lesson.
|
|
type templateSection struct {
|
|
Type string
|
|
Title string
|
|
Number int
|
|
Description string
|
|
Items []templateItem
|
|
}
|
|
|
|
// templateItem represents a course item with preprocessed data.
|
|
type templateItem struct {
|
|
Type string
|
|
TypeTitle string
|
|
Items []templateSubItem
|
|
}
|
|
|
|
// templateSubItem represents a sub-item with preprocessed data.
|
|
type templateSubItem struct {
|
|
Heading string
|
|
Paragraph string
|
|
Title string
|
|
Caption string
|
|
CleanText string
|
|
Answers []models.Answer
|
|
Feedback string
|
|
Media *models.Media
|
|
}
|
|
|
|
// prepareTemplateData converts a Course model into template-friendly data.
|
|
func prepareTemplateData(course *models.Course, htmlCleaner *services.HTMLCleaner) *templateData {
|
|
data := &templateData{
|
|
Course: course.Course,
|
|
ShareID: course.ShareID,
|
|
Sections: make([]templateSection, 0, len(course.Course.Lessons)),
|
|
CSS: defaultCSS,
|
|
}
|
|
|
|
lessonCounter := 0
|
|
for _, lesson := range course.Course.Lessons {
|
|
section := templateSection{
|
|
Type: lesson.Type,
|
|
Title: lesson.Title,
|
|
Description: lesson.Description,
|
|
}
|
|
|
|
if lesson.Type != lessonTypeSection {
|
|
lessonCounter++
|
|
section.Number = lessonCounter
|
|
section.Items = prepareItems(lesson.Items, htmlCleaner)
|
|
}
|
|
|
|
data.Sections = append(data.Sections, section)
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
// prepareItems converts model Items to template Items.
|
|
func prepareItems(items []models.Item, htmlCleaner *services.HTMLCleaner) []templateItem {
|
|
result := make([]templateItem, 0, len(items))
|
|
|
|
for _, item := range items {
|
|
tItem := templateItem{
|
|
Type: strings.ToLower(item.Type),
|
|
Items: make([]templateSubItem, 0, len(item.Items)),
|
|
}
|
|
|
|
// Set type title for unknown items
|
|
if tItem.Type != itemTypeText && tItem.Type != itemTypeList && tItem.Type != itemTypeKnowledgeCheck &&
|
|
tItem.Type != itemTypeMultimedia && tItem.Type != itemTypeImage && tItem.Type != itemTypeInteractive &&
|
|
tItem.Type != itemTypeDivider {
|
|
caser := cases.Title(language.English)
|
|
tItem.TypeTitle = caser.String(item.Type)
|
|
}
|
|
|
|
// Process sub-items
|
|
for _, subItem := range item.Items {
|
|
tSubItem := templateSubItem{
|
|
Heading: subItem.Heading,
|
|
Paragraph: subItem.Paragraph,
|
|
Title: subItem.Title,
|
|
Caption: subItem.Caption,
|
|
Answers: subItem.Answers,
|
|
Feedback: subItem.Feedback,
|
|
Media: subItem.Media,
|
|
}
|
|
|
|
// Clean HTML for list items
|
|
if tItem.Type == itemTypeList && subItem.Paragraph != "" {
|
|
tSubItem.CleanText = htmlCleaner.CleanHTML(subItem.Paragraph)
|
|
}
|
|
|
|
tItem.Items = append(tItem.Items, tSubItem)
|
|
}
|
|
|
|
result = append(result, tItem)
|
|
}
|
|
|
|
return result
|
|
}
|