1 Commits

Author SHA1 Message Date
dependabot[bot] 3b673992fd docker(deps): bump golang in the docker group across 1 directory
Bumps the docker group with 1 update in the / directory: golang.


Updates `golang` from 1.25-alpine to 1.26-alpine

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.26-alpine
  dependency-type: direct:production
  dependency-group: docker
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-08 23:53:15 +00:00
12 changed files with 75 additions and 92 deletions
+22 -28
View File
@@ -35,24 +35,21 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
strategy:
matrix:
go:
- 1.24.x
- 1.25.x
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Set up Go - name: Set up Go ${{ matrix.go }}
id: setup-go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version-file: go.mod go-version: ${{ matrix.go }}
check-latest: true check-latest: true
# Expose the resolved Go version as an env var so it is never
# interpolated directly into a shell script (avoids script injection).
- name: Export Go version
env:
GO_VERSION: ${{ steps.setup-go.outputs.go-version }}
run: echo "GO_VERSION=$GO_VERSION" >> "$GITHUB_ENV"
- name: Install Task - name: Install Task
uses: go-task/setup-task@v1 uses: go-task/setup-task@v1
@@ -73,7 +70,7 @@ jobs:
{ {
cat << EOF cat << EOF
## 🔧 Test Environment ## 🔧 Test Environment
- **Go Version:** $GO_VERSION - **Go Version:** ${{ matrix.go }}
- **OS:** ubuntu-latest - **OS:** ubuntu-latest
- **Timestamp:** $(date -u) - **Timestamp:** $(date -u)
@@ -93,7 +90,7 @@ jobs:
# Generate test summary # Generate test summary
{ {
cat << EOF cat << EOF
## 🧪 Test Results (Go $GO_VERSION) ## 🧪 Test Results (Go ${{ matrix.go }})
| Metric | Value | | Metric | Value |
| ----------- | ------------------------------------------------------------- | | ----------- | ------------------------------------------------------------- |
@@ -155,7 +152,7 @@ jobs:
{ {
cat << EOF cat << EOF
## 📊 Code Coverage (Go $GO_VERSION) ## 📊 Code Coverage (Go ${{ matrix.go }})
**Total Coverage: $COVERAGE** **Total Coverage: $COVERAGE**
@@ -214,9 +211,9 @@ jobs:
- name: Upload test artifacts - name: Upload test artifacts
if: failure() if: failure()
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v6
with: with:
name: test-results-go-${{ env.GO_VERSION }} name: test-results-go-${{ matrix.go }}
path: | path: |
test-output.log test-output.log
coverage/ coverage/
@@ -226,7 +223,7 @@ jobs:
run: | run: |
{ {
cat << EOF cat << EOF
## 🔍 Static Analysis (Go $GO_VERSION) ## 🔍 Static Analysis (Go ${{ matrix.go }})
EOF EOF
@@ -270,10 +267,9 @@ jobs:
- name: Job Summary - name: Job Summary
if: always() if: always()
run: | run: |
{ cat >> "$GITHUB_STEP_SUMMARY" << 'EOF'
echo "## 📋 Job Summary (Go $GO_VERSION)" ## 📋 Job Summary (Go ${{ matrix.go }})
echo ""
cat << 'EOF'
| Step | Status | | Step | Status |
| --------------- | --------------------------------------------------------------- | | --------------- | --------------------------------------------------------------- |
| Dependencies | Success | | Dependencies | Success |
@@ -283,13 +279,12 @@ jobs:
| Static Analysis | ${{ job.status == 'success' && 'Clean' || 'Issues' }} | | Static Analysis | ${{ job.status == 'success' && 'Clean' || 'Issues' }} |
| Code Formatting | ${{ job.status == 'success' && 'Clean' || 'Issues' }} | | Code Formatting | ${{ job.status == 'success' && 'Clean' || 'Issues' }} |
EOF EOF
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload coverage reports to Codecov - name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5 uses: codecov/codecov-action@v5
with: with:
files: ./coverage/coverage.out files: ./coverage/coverage.out
flags: Go ${{ env.GO_VERSION }} flags: Go ${{ matrix.go }}
slug: kjanat/articulate-parser slug: kjanat/articulate-parser
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
@@ -297,7 +292,7 @@ jobs:
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
uses: codecov/test-results-action@v1 uses: codecov/test-results-action@v1
with: with:
flags: Go ${{ env.GO_VERSION }} flags: Go ${{ matrix.go }}
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
docker-test: docker-test:
@@ -353,14 +348,13 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
pull-requests: write
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
- name: "Checkout Repository" - name: "Checkout Repository"
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: "Dependency Review" - name: "Dependency Review"
uses: actions/dependency-review-action@v5 uses: actions/dependency-review-action@v4
with: with:
fail-on-severity: moderate fail-on-severity: moderate
comment-summary-in-pr: always comment-summary-in-pr: always
@@ -381,20 +375,20 @@ jobs:
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v4 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: "Dependency Review" - name: "Dependency Review"
uses: actions/dependency-review-action@v5 uses: actions/dependency-review-action@v4
with: with:
fail-on-severity: moderate fail-on-severity: moderate
comment-summary-in-pr: always comment-summary-in-pr: always
+4 -4
View File
@@ -62,7 +62,7 @@ jobs:
done done
- name: Create Release - name: Create Release
uses: softprops/action-gh-release@v3 uses: softprops/action-gh-release@v2
with: with:
files: | files: |
build/articulate-parser-linux-amd64 build/articulate-parser-linux-amd64
@@ -91,20 +91,20 @@ jobs:
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
username: ${{ vars.DOCKERHUB_USERNAME }} username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v4 uses: docker/login-action@v3
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v4 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
+5
View File
@@ -8,6 +8,11 @@ run:
# Timeout for total work # Timeout for total work
timeout: 5m timeout: 5m
# Skip directories (not allowed in config v2, will use issues exclude instead)
# Go version
go: "1.24"
# Include test files # Include test files
tests: true tests: true
+7 -7
View File
@@ -78,12 +78,12 @@ flowchart TD
The system follows **Clean Architecture** principles with clear separation of concerns: The system follows **Clean Architecture** principles with clear separation of concerns:
- **Entry Point**: Command-line interface handles user input and coordinates operations - **🎯 Entry Point**: Command-line interface handles user input and coordinates operations
- **Application Layer**: Core business logic with dependency injection - **🏗️ Application Layer**: Core business logic with dependency injection
- **Interface Layer**: Contracts defining behavior without implementation details - **📋 Interface Layer**: Contracts defining behavior without implementation details
- **Service Layer**: Concrete implementations of parsing and utility services - **🔧 Service Layer**: Concrete implementations of parsing and utility services
- **Export Layer**: Factory pattern for format-specific exporters - **📤 Export Layer**: Factory pattern for format-specific exporters
- **Data Layer**: Domain models representing course structure - **📊 Data Layer**: Domain models representing course structure
## Features ## Features
@@ -206,7 +206,7 @@ Then run:
The application is available as a Docker image from GitHub Container Registry. The application is available as a Docker image from GitHub Container Registry.
### Docker Image Information ### 🐳 Docker Image Information
- **Registry**: `ghcr.io/kjanat/articulate-parser` - **Registry**: `ghcr.io/kjanat/articulate-parser`
- **Platforms**: linux/amd64, linux/arm64 - **Platforms**: linux/amd64, linux/arm64
+6 -4
View File
@@ -1,14 +1,16 @@
module github.com/kjanat/articulate-parser module github.com/kjanat/articulate-parser
go 1.26.0 go 1.24.0
toolchain go1.25.5
require ( require (
github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b
golang.org/x/net v0.56.0 golang.org/x/net v0.50.0
golang.org/x/text v0.38.0 golang.org/x/text v0.34.0
) )
require ( require (
github.com/fumiama/imgsz v0.0.4 // indirect github.com/fumiama/imgsz v0.0.4 // indirect
golang.org/x/image v0.42.0 // indirect golang.org/x/image v0.34.0 // indirect
) )
+6 -6
View File
@@ -2,9 +2,9 @@ github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b h1:/mxSugRc4SgN7Xg
github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b/go.mod h1:ssRF0IaB1hCcKIObp3FkZOsjTcAHpgii70JelNb4H8M= github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b/go.mod h1:ssRF0IaB1hCcKIObp3FkZOsjTcAHpgii70JelNb4H8M=
github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI= github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI=
github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E= github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E=
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY= golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q= golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
+4 -13
View File
@@ -13,11 +13,6 @@ 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.
@@ -46,11 +41,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, formatAliasMarkdown: case FormatMarkdown, "md":
return NewMarkdownExporter(f.htmlCleaner), nil return NewMarkdownExporter(f.htmlCleaner), nil
case FormatDocx, formatAliasDocx: case FormatDocx, "word":
return NewDocxExporter(f.htmlCleaner), nil return NewDocxExporter(f.htmlCleaner), nil
case FormatHTML, formatAliasHTML: case FormatHTML, "htm":
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)
@@ -60,9 +55,5 @@ 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{ return []string{FormatMarkdown, "md", FormatDocx, "word", FormatHTML, "htm"}
FormatMarkdown, formatAliasMarkdown,
FormatDocx, formatAliasDocx,
FormatHTML, formatAliasHTML,
}
} }
+1 -4
View File
@@ -21,9 +21,6 @@ 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
@@ -77,7 +74,7 @@ func prepareTemplateData(course *models.Course, htmlCleaner *services.HTMLCleane
Description: lesson.Description, Description: lesson.Description,
} }
if lesson.Type != lessonTypeSection { if lesson.Type != "section" {
lessonCounter++ lessonCounter++
section.Number = lessonCounter section.Number = lessonCounter
section.Items = prepareItems(lesson.Items, htmlCleaner) section.Items = prepareItems(lesson.Items, htmlCleaner)
+17 -17
View File
@@ -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
fmt.Fprintf(&buf, "# %s\n\n", course.Course.Title) buf.WriteString(fmt.Sprintf("# %s\n\n", course.Course.Title))
if course.Course.Description != "" { if course.Course.Description != "" {
fmt.Fprintf(&buf, "%s\n\n", e.htmlCleaner.CleanHTML(course.Course.Description)) buf.WriteString(fmt.Sprintf("%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")
fmt.Fprintf(&buf, "- **Course ID**: %s\n", course.Course.ID) buf.WriteString(fmt.Sprintf("- **Course ID**: %s\n", course.Course.ID))
fmt.Fprintf(&buf, "- **Share ID**: %s\n", course.ShareID) buf.WriteString(fmt.Sprintf("- **Share ID**: %s\n", course.ShareID))
fmt.Fprintf(&buf, "- **Navigation Mode**: %s\n", course.Course.NavigationMode) buf.WriteString(fmt.Sprintf("- **Navigation Mode**: %s\n", course.Course.NavigationMode))
if course.Course.ExportSettings != nil { if course.Course.ExportSettings != nil {
fmt.Fprintf(&buf, "- **Export Format**: %s\n", course.Course.ExportSettings.Format) buf.WriteString(fmt.Sprintf("- **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 == lessonTypeSection { if lesson.Type == "section" {
fmt.Fprintf(&buf, "# %s\n\n", lesson.Title) buf.WriteString(fmt.Sprintf("# %s\n\n", lesson.Title))
continue continue
} }
lessonCounter++ lessonCounter++
fmt.Fprintf(&buf, "## Lesson %d: %s\n\n", lessonCounter, lesson.Title) buf.WriteString(fmt.Sprintf("## Lesson %d: %s\n\n", lessonCounter, lesson.Title))
if lesson.Description != "" { if lesson.Description != "" {
fmt.Fprintf(&buf, "%s\n\n", e.htmlCleaner.CleanHTML(lesson.Description)) buf.WriteString(fmt.Sprintf("%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 itemTypeText: case "text":
e.processTextItem(buf, item, headingPrefix) e.processTextItem(buf, item, headingPrefix)
case itemTypeList: case "list":
e.processListItem(buf, item) e.processListItem(buf, item)
case itemTypeMultimedia: case "multimedia":
e.processMultimediaItem(buf, item, headingPrefix) e.processMultimediaItem(buf, item, headingPrefix)
case itemTypeImage: case "image":
e.processImageItem(buf, item, headingPrefix) e.processImageItem(buf, item, headingPrefix)
case itemTypeKnowledgeCheck: case "knowledgecheck":
e.processKnowledgeCheckItem(buf, item, headingPrefix) e.processKnowledgeCheckItem(buf, item, headingPrefix)
case itemTypeInteractive: case "interactive":
e.processInteractiveItem(buf, item, headingPrefix) e.processInteractiveItem(buf, item, headingPrefix)
case itemTypeDivider: case "divider":
e.processDividerItem(buf) e.processDividerItem(buf)
default: default:
e.processUnknownItem(buf, item, headingPrefix) e.processUnknownItem(buf, item, headingPrefix)
Binary file not shown.
+2 -8
View File
@@ -15,12 +15,6 @@ import (
"github.com/kjanat/articulate-parser/internal/models" "github.com/kjanat/articulate-parser/internal/models"
) )
// Default endpoint configuration for the Articulate Rise API.
const (
riseHost = "rise.articulate.com"
defaultBaseURL = "https://" + riseHost
)
// 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_-]+)`)
@@ -43,7 +37,7 @@ func NewArticulateParser(logger interfaces.Logger, baseURL string, timeout time.
logger = NewNoOpLogger() logger = NewNoOpLogger()
} }
if baseURL == "" { if baseURL == "" {
baseURL = defaultBaseURL baseURL = "https://rise.articulate.com"
} }
if timeout == 0 { if timeout == 0 {
timeout = 30 * time.Second timeout = 30 * time.Second
@@ -138,7 +132,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 != riseHost { if parsedURL.Host != "rise.articulate.com" {
return "", fmt.Errorf("invalid domain for Articulate Rise URI: %s", parsedURL.Host) return "", fmt.Errorf("invalid domain for Articulate Rise URI: %s", parsedURL.Host)
} }