mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-08-05 18:04:07 +02:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 35bb25e405 | |||
| 43a83e4004 | |||
| d966bd1aa4 | |||
| 144125d355 | |||
| 9ea51458c3 | |||
| 9b31fcda9e | |||
| 6e5c6aa086 | |||
| 6ba44a75eb | |||
| 5890b1d3b9 |
+28
-22
@@ -35,21 +35,24 @@ 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 ${{ matrix.go }}
|
- name: Set up Go
|
||||||
|
id: setup-go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version-file: go.mod
|
||||||
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
|
||||||
|
|
||||||
@@ -70,7 +73,7 @@ jobs:
|
|||||||
{
|
{
|
||||||
cat << EOF
|
cat << EOF
|
||||||
## 🔧 Test Environment
|
## 🔧 Test Environment
|
||||||
- **Go Version:** ${{ matrix.go }}
|
- **Go Version:** $GO_VERSION
|
||||||
- **OS:** ubuntu-latest
|
- **OS:** ubuntu-latest
|
||||||
- **Timestamp:** $(date -u)
|
- **Timestamp:** $(date -u)
|
||||||
|
|
||||||
@@ -90,7 +93,7 @@ jobs:
|
|||||||
# Generate test summary
|
# Generate test summary
|
||||||
{
|
{
|
||||||
cat << EOF
|
cat << EOF
|
||||||
## 🧪 Test Results (Go ${{ matrix.go }})
|
## 🧪 Test Results (Go $GO_VERSION)
|
||||||
|
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
| ----------- | ------------------------------------------------------------- |
|
| ----------- | ------------------------------------------------------------- |
|
||||||
@@ -152,7 +155,7 @@ jobs:
|
|||||||
|
|
||||||
{
|
{
|
||||||
cat << EOF
|
cat << EOF
|
||||||
## 📊 Code Coverage (Go ${{ matrix.go }})
|
## 📊 Code Coverage (Go $GO_VERSION)
|
||||||
|
|
||||||
**Total Coverage: $COVERAGE**
|
**Total Coverage: $COVERAGE**
|
||||||
|
|
||||||
@@ -213,7 +216,7 @@ jobs:
|
|||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: test-results-go-${{ matrix.go }}
|
name: test-results-go-${{ env.GO_VERSION }}
|
||||||
path: |
|
path: |
|
||||||
test-output.log
|
test-output.log
|
||||||
coverage/
|
coverage/
|
||||||
@@ -223,7 +226,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
{
|
{
|
||||||
cat << EOF
|
cat << EOF
|
||||||
## 🔍 Static Analysis (Go ${{ matrix.go }})
|
## 🔍 Static Analysis (Go $GO_VERSION)
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -267,9 +270,10 @@ jobs:
|
|||||||
- name: Job Summary
|
- name: Job Summary
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
cat >> "$GITHUB_STEP_SUMMARY" << 'EOF'
|
{
|
||||||
## 📋 Job Summary (Go ${{ matrix.go }})
|
echo "## 📋 Job Summary (Go $GO_VERSION)"
|
||||||
|
echo ""
|
||||||
|
cat << 'EOF'
|
||||||
| Step | Status |
|
| Step | Status |
|
||||||
| --------------- | --------------------------------------------------------------- |
|
| --------------- | --------------------------------------------------------------- |
|
||||||
| Dependencies | Success |
|
| Dependencies | Success |
|
||||||
@@ -279,12 +283,13 @@ 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 ${{ matrix.go }}
|
flags: Go ${{ env.GO_VERSION }}
|
||||||
slug: kjanat/articulate-parser
|
slug: kjanat/articulate-parser
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
@@ -292,7 +297,7 @@ jobs:
|
|||||||
if: ${{ !cancelled() }}
|
if: ${{ !cancelled() }}
|
||||||
uses: codecov/test-results-action@v1
|
uses: codecov/test-results-action@v1
|
||||||
with:
|
with:
|
||||||
flags: Go ${{ matrix.go }}
|
flags: Go ${{ env.GO_VERSION }}
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
docker-test:
|
docker-test:
|
||||||
@@ -348,13 +353,14 @@ 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@v4
|
uses: actions/dependency-review-action@v5
|
||||||
with:
|
with:
|
||||||
fail-on-severity: moderate
|
fail-on-severity: moderate
|
||||||
comment-summary-in-pr: always
|
comment-summary-in-pr: always
|
||||||
@@ -375,20 +381,20 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
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@v3
|
uses: docker/login-action@v4
|
||||||
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@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -416,7 +422,7 @@ jobs:
|
|||||||
org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/master/DOCKER.md
|
org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/master/DOCKER.md
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
# Multi-architecture build - Docker automatically provides TARGETOS, TARGETARCH, etc.
|
# Multi-architecture build - Docker automatically provides TARGETOS, TARGETARCH, etc.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: "Dependency Review"
|
- name: "Dependency Review"
|
||||||
uses: actions/dependency-review-action@v4
|
uses: actions/dependency-review-action@v5
|
||||||
with:
|
with:
|
||||||
fail-on-severity: moderate
|
fail-on-severity: moderate
|
||||||
comment-summary-in-pr: always
|
comment-summary-in-pr: always
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v3
|
||||||
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@v3
|
uses: docker/login-action@v4
|
||||||
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@v3
|
uses: docker/login-action@v4
|
||||||
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@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/master/DOCKER.md
|
org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/master/DOCKER.md
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: |
|
platforms: |
|
||||||
|
|||||||
@@ -8,11 +8,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
FROM golang:1.25-alpine AS builder
|
FROM golang:1.26-alpine AS builder
|
||||||
|
|
||||||
# Install git and ca-certificates (needed for fetching dependencies and HTTPS)
|
# Install git and ca-certificates (needed for fetching dependencies and HTTPS)
|
||||||
RUN apk add --no-cache git ca-certificates tzdata file
|
RUN apk add --no-cache git ca-certificates tzdata file
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
# Uses Alpine instead of scratch for debugging
|
# Uses Alpine instead of scratch for debugging
|
||||||
|
|
||||||
# Build stage - same as production
|
# Build stage - same as production
|
||||||
FROM golang:1.25-alpine AS builder
|
FROM golang:1.26-alpine AS builder
|
||||||
|
|
||||||
# Install git and ca-certificates (needed for fetching dependencies and HTTPS)
|
# Install git and ca-certificates (needed for fetching dependencies and HTTPS)
|
||||||
RUN apk add --no-cache git ca-certificates tzdata file
|
RUN apk add --no-cache git ca-certificates tzdata 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
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
module github.com/kjanat/articulate-parser
|
module github.com/kjanat/articulate-parser
|
||||||
|
|
||||||
go 1.24.0
|
go 1.26.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.49.0
|
golang.org/x/net v0.56.0
|
||||||
golang.org/x/text v0.33.0
|
golang.org/x/text v0.38.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fumiama/imgsz v0.0.4 // indirect
|
github.com/fumiama/imgsz v0.0.4 // indirect
|
||||||
golang.org/x/image v0.34.0 // indirect
|
golang.org/x/image v0.42.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
golang.org/x/image v0.42.0 h1:1gSs6ehNWXLbkHBIPcWztk3D/6aIA/8hauiAYtlodVY=
|
||||||
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
golang.org/x/image v0.42.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Binary file not shown.
@@ -15,6 +15,12 @@ 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_-]+)`)
|
||||||
|
|
||||||
@@ -37,7 +43,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 +138,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