mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-10-13 00:55:27 +02:00
refactor: extract base exporter, add sentinel errors, document unused fields
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
// Package apperrors provides sentinel errors for the articulate-parser application.
|
||||||
|
package apperrors
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Export errors.
|
||||||
|
var (
|
||||||
|
// ErrUnsupportedFormat is returned when an unknown export format is requested.
|
||||||
|
ErrUnsupportedFormat = errors.New("unsupported export format")
|
||||||
|
)
|
||||||
|
|
||||||
|
// URI parsing errors.
|
||||||
|
var (
|
||||||
|
// ErrInvalidURI is returned when a URI cannot be parsed.
|
||||||
|
ErrInvalidURI = errors.New("invalid URI")
|
||||||
|
|
||||||
|
// ErrInvalidDomain is returned when the URI domain is not an Articulate Rise domain.
|
||||||
|
ErrInvalidDomain = errors.New("invalid domain for Articulate Rise URI")
|
||||||
|
|
||||||
|
// ErrNoShareID is returned when a share ID cannot be extracted from the URI.
|
||||||
|
ErrNoShareID = errors.New("could not extract share ID from URI")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Operation errors.
|
||||||
|
var (
|
||||||
|
// ErrOperationCanceled is returned when an operation is canceled via context.
|
||||||
|
ErrOperationCanceled = errors.New("operation canceled")
|
||||||
|
)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package apperrors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSentinelErrors_Unwrap(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
sentinel error
|
||||||
|
wrapped error
|
||||||
|
}{
|
||||||
|
{"ErrUnsupportedFormat", ErrUnsupportedFormat, fmt.Errorf("%w: xyz", ErrUnsupportedFormat)},
|
||||||
|
{"ErrInvalidURI", ErrInvalidURI, fmt.Errorf("%w: bad", ErrInvalidURI)},
|
||||||
|
{"ErrInvalidDomain", ErrInvalidDomain, fmt.Errorf("%w: evil.com", ErrInvalidDomain)},
|
||||||
|
{"ErrNoShareID", ErrNoShareID, fmt.Errorf("%w: no match", ErrNoShareID)},
|
||||||
|
{"ErrOperationCanceled", ErrOperationCanceled, fmt.Errorf("%w: ctx done", ErrOperationCanceled)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if !errors.Is(tt.wrapped, tt.sentinel) {
|
||||||
|
t.Errorf("errors.Is(%v, %v) = false, want true", tt.wrapped, tt.sentinel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSentinelErrors_Message(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
err error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{ErrUnsupportedFormat, "unsupported export format"},
|
||||||
|
{ErrInvalidURI, "invalid URI"},
|
||||||
|
{ErrInvalidDomain, "invalid domain for Articulate Rise URI"},
|
||||||
|
{ErrNoShareID, "could not extract share ID from URI"},
|
||||||
|
{ErrOperationCanceled, "operation canceled"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.expected, func(t *testing.T) {
|
||||||
|
if tt.err.Error() != tt.expected {
|
||||||
|
t.Errorf("got %q, want %q", tt.err.Error(), tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package exporters
|
||||||
|
|
||||||
|
import "github.com/kjanat/articulate-parser/internal/interfaces"
|
||||||
|
|
||||||
|
// baseExporter contains common fields shared by all exporter implementations.
|
||||||
|
// Embed this struct in concrete exporters to inherit HTMLCleaner and Logger.
|
||||||
|
type baseExporter struct {
|
||||||
|
// htmlCleaner is used to convert HTML content to plain text.
|
||||||
|
htmlCleaner interfaces.HTMLCleaner
|
||||||
|
// logger is used for logging warnings and errors.
|
||||||
|
logger interfaces.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBaseExporter creates a new baseExporter with the given dependencies.
|
||||||
|
func newBaseExporter(htmlCleaner interfaces.HTMLCleaner, logger interfaces.Logger) baseExporter {
|
||||||
|
return baseExporter{
|
||||||
|
htmlCleaner: htmlCleaner,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,10 +26,7 @@ const (
|
|||||||
// It converts Articulate Rise course data into a Microsoft Word document
|
// It converts Articulate Rise course data into a Microsoft Word document
|
||||||
// using the go-docx package.
|
// using the go-docx package.
|
||||||
type DocxExporter struct {
|
type DocxExporter struct {
|
||||||
// htmlCleaner is used to convert HTML content to plain text
|
baseExporter
|
||||||
htmlCleaner interfaces.HTMLCleaner
|
|
||||||
// logger is used for logging warnings and errors
|
|
||||||
logger interfaces.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDocxExporter creates a new DocxExporter instance.
|
// NewDocxExporter creates a new DocxExporter instance.
|
||||||
@@ -43,8 +40,7 @@ type DocxExporter struct {
|
|||||||
// - An implementation of the Exporter interface for DOCX format
|
// - An implementation of the Exporter interface for DOCX format
|
||||||
func NewDocxExporter(htmlCleaner interfaces.HTMLCleaner, logger interfaces.Logger) interfaces.Exporter {
|
func NewDocxExporter(htmlCleaner interfaces.HTMLCleaner, logger interfaces.Logger) interfaces.Exporter {
|
||||||
return &DocxExporter{
|
return &DocxExporter{
|
||||||
htmlCleaner: htmlCleaner,
|
baseExporter: newBaseExporter(htmlCleaner, logger),
|
||||||
logger: logger,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kjanat/articulate-parser/internal/apperrors"
|
||||||
"github.com/kjanat/articulate-parser/internal/interfaces"
|
"github.com/kjanat/articulate-parser/internal/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ func (f *Factory) CreateExporter(format string) (interfaces.Exporter, error) {
|
|||||||
case FormatHTML, formatAliasHTML:
|
case FormatHTML, formatAliasHTML:
|
||||||
return NewHTMLExporter(f.htmlCleaner, f.logger), nil
|
return NewHTMLExporter(f.htmlCleaner, f.logger), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unsupported export format: %s", format)
|
return nil, fmt.Errorf("%w: %s", apperrors.ErrUnsupportedFormat, format)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,9 @@ var htmlTemplate string
|
|||||||
// HTMLExporter implements the Exporter interface for HTML format.
|
// HTMLExporter implements the Exporter interface for HTML format.
|
||||||
// It converts Articulate Rise course data into a structured HTML document using templates.
|
// It converts Articulate Rise course data into a structured HTML document using templates.
|
||||||
type HTMLExporter struct {
|
type HTMLExporter struct {
|
||||||
// htmlCleaner is used to convert HTML content to plain text when needed
|
baseExporter
|
||||||
htmlCleaner interfaces.HTMLCleaner
|
// tmpl holds the parsed HTML template.
|
||||||
// tmpl holds the parsed HTML template
|
|
||||||
tmpl *template.Template
|
tmpl *template.Template
|
||||||
// logger is used for logging warnings and errors
|
|
||||||
logger interfaces.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTMLExporter creates a new HTMLExporter instance.
|
// NewHTMLExporter creates a new HTMLExporter instance.
|
||||||
@@ -52,9 +49,8 @@ func NewHTMLExporter(htmlCleaner interfaces.HTMLCleaner, logger interfaces.Logge
|
|||||||
tmpl := template.Must(template.New("html").Funcs(funcMap).Parse(htmlTemplate))
|
tmpl := template.Must(template.New("html").Funcs(funcMap).Parse(htmlTemplate))
|
||||||
|
|
||||||
return &HTMLExporter{
|
return &HTMLExporter{
|
||||||
htmlCleaner: htmlCleaner,
|
baseExporter: newBaseExporter(htmlCleaner, logger),
|
||||||
tmpl: tmpl,
|
tmpl: tmpl,
|
||||||
logger: logger,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,10 +16,7 @@ import (
|
|||||||
// MarkdownExporter implements the Exporter interface for Markdown format.
|
// MarkdownExporter implements the Exporter interface for Markdown format.
|
||||||
// It converts Articulate Rise course data into a structured Markdown document.
|
// It converts Articulate Rise course data into a structured Markdown document.
|
||||||
type MarkdownExporter struct {
|
type MarkdownExporter struct {
|
||||||
// htmlCleaner is used to convert HTML content to plain text
|
baseExporter
|
||||||
htmlCleaner interfaces.HTMLCleaner
|
|
||||||
// logger is used for logging warnings and errors
|
|
||||||
logger interfaces.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMarkdownExporter creates a new MarkdownExporter instance.
|
// NewMarkdownExporter creates a new MarkdownExporter instance.
|
||||||
@@ -33,8 +30,7 @@ type MarkdownExporter struct {
|
|||||||
// - An implementation of the Exporter interface for Markdown format
|
// - An implementation of the Exporter interface for Markdown format
|
||||||
func NewMarkdownExporter(htmlCleaner interfaces.HTMLCleaner, logger interfaces.Logger) interfaces.Exporter {
|
func NewMarkdownExporter(htmlCleaner interfaces.HTMLCleaner, logger interfaces.Logger) interfaces.Exporter {
|
||||||
return &MarkdownExporter{
|
return &MarkdownExporter{
|
||||||
htmlCleaner: htmlCleaner,
|
baseExporter: newBaseExporter(htmlCleaner, logger),
|
||||||
logger: logger,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ func TestMarkdownExporter_Export_InvalidPath(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessTextItem tests the processTextItem method.
|
// TestMarkdownExporter_ProcessTextItem tests the processTextItem method.
|
||||||
func TestMarkdownExporter_ProcessTextItem(t *testing.T) {
|
func TestMarkdownExporter_ProcessTextItem(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
@@ -153,7 +153,7 @@ func TestMarkdownExporter_ProcessTextItem(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessListItem tests the processListItem method.
|
// TestMarkdownExporter_ProcessListItem tests the processListItem method.
|
||||||
func TestMarkdownExporter_ProcessListItem(t *testing.T) {
|
func TestMarkdownExporter_ProcessListItem(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
@@ -178,7 +178,7 @@ func TestMarkdownExporter_ProcessListItem(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessMultimediaItem tests the processMultimediaItem method.
|
// TestMarkdownExporter_ProcessMultimediaItem tests the processMultimediaItem method.
|
||||||
func TestMarkdownExporter_ProcessMultimediaItem(t *testing.T) {
|
func TestMarkdownExporter_ProcessMultimediaItem(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
@@ -217,7 +217,7 @@ func TestMarkdownExporter_ProcessMultimediaItem(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessImageItem tests the processImageItem method.
|
// TestMarkdownExporter_ProcessImageItem tests the processImageItem method.
|
||||||
func TestMarkdownExporter_ProcessImageItem(t *testing.T) {
|
func TestMarkdownExporter_ProcessImageItem(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
@@ -252,7 +252,7 @@ func TestMarkdownExporter_ProcessImageItem(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessKnowledgeCheckItem tests the processKnowledgeCheckItem method.
|
// TestMarkdownExporter_ProcessKnowledgeCheckItem tests the processKnowledgeCheckItem method.
|
||||||
func TestMarkdownExporter_ProcessKnowledgeCheckItem(t *testing.T) {
|
func TestMarkdownExporter_ProcessKnowledgeCheckItem(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
@@ -294,7 +294,7 @@ func TestMarkdownExporter_ProcessKnowledgeCheckItem(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessInteractiveItem tests the processInteractiveItem method.
|
// TestMarkdownExporter_ProcessInteractiveItem tests the processInteractiveItem method.
|
||||||
func TestMarkdownExporter_ProcessInteractiveItem(t *testing.T) {
|
func TestMarkdownExporter_ProcessInteractiveItem(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
@@ -319,7 +319,7 @@ func TestMarkdownExporter_ProcessInteractiveItem(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessDividerItem tests the processDividerItem method.
|
// TestMarkdownExporter_ProcessDividerItem tests the processDividerItem method.
|
||||||
func TestMarkdownExporter_ProcessDividerItem(t *testing.T) {
|
func TestMarkdownExporter_ProcessDividerItem(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
exporter.processDividerItem(&buf)
|
exporter.processDividerItem(&buf)
|
||||||
@@ -335,7 +335,7 @@ func TestMarkdownExporter_ProcessDividerItem(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessUnknownItem tests the processUnknownItem method.
|
// TestMarkdownExporter_ProcessUnknownItem tests the processUnknownItem method.
|
||||||
func TestMarkdownExporter_ProcessUnknownItem(t *testing.T) {
|
func TestMarkdownExporter_ProcessUnknownItem(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
@@ -366,7 +366,7 @@ func TestMarkdownExporter_ProcessUnknownItem(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessVideoMedia tests the processVideoMedia method.
|
// TestMarkdownExporter_ProcessVideoMedia tests the processVideoMedia method.
|
||||||
func TestMarkdownExporter_ProcessVideoMedia(t *testing.T) {
|
func TestMarkdownExporter_ProcessVideoMedia(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
media := &models.Media{
|
media := &models.Media{
|
||||||
@@ -391,7 +391,7 @@ func TestMarkdownExporter_ProcessVideoMedia(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessImageMedia tests the processImageMedia method.
|
// TestMarkdownExporter_ProcessImageMedia tests the processImageMedia method.
|
||||||
func TestMarkdownExporter_ProcessImageMedia(t *testing.T) {
|
func TestMarkdownExporter_ProcessImageMedia(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
media := &models.Media{
|
media := &models.Media{
|
||||||
@@ -413,7 +413,7 @@ func TestMarkdownExporter_ProcessImageMedia(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessAnswers tests the processAnswers method.
|
// TestMarkdownExporter_ProcessAnswers tests the processAnswers method.
|
||||||
func TestMarkdownExporter_ProcessAnswers(t *testing.T) {
|
func TestMarkdownExporter_ProcessAnswers(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
answers := []models.Answer{
|
answers := []models.Answer{
|
||||||
@@ -443,7 +443,7 @@ func TestMarkdownExporter_ProcessAnswers(t *testing.T) {
|
|||||||
// TestMarkdownExporter_ProcessItemToMarkdown_AllTypes tests all item types.
|
// TestMarkdownExporter_ProcessItemToMarkdown_AllTypes tests all item types.
|
||||||
func TestMarkdownExporter_ProcessItemToMarkdown_AllTypes(t *testing.T) {
|
func TestMarkdownExporter_ProcessItemToMarkdown_AllTypes(t *testing.T) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -673,7 +673,7 @@ func BenchmarkMarkdownExporter_Export(b *testing.B) {
|
|||||||
// BenchmarkMarkdownExporter_ProcessTextItem benchmarks the processTextItem method.
|
// BenchmarkMarkdownExporter_ProcessTextItem benchmarks the processTextItem method.
|
||||||
func BenchmarkMarkdownExporter_ProcessTextItem(b *testing.B) {
|
func BenchmarkMarkdownExporter_ProcessTextItem(b *testing.B) {
|
||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
exporter := &MarkdownExporter{htmlCleaner: htmlCleaner}
|
exporter := &MarkdownExporter{baseExporter: baseExporter{htmlCleaner: htmlCleaner}}
|
||||||
|
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
Type: "text",
|
Type: "text",
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ type ExportSettings struct {
|
|||||||
|
|
||||||
// LabelSet contains customized labels used throughout the course.
|
// LabelSet contains customized labels used throughout the course.
|
||||||
// This allows course creators to modify standard terminology.
|
// This allows course creators to modify standard terminology.
|
||||||
|
//
|
||||||
|
// NOTE: Currently parsed but not used in export logic.
|
||||||
type LabelSet struct {
|
type LabelSet struct {
|
||||||
// ID is the unique identifier for this label set
|
// ID is the unique identifier for this label set
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ type Lesson struct {
|
|||||||
Icon string `json:"icon"`
|
Icon string `json:"icon"`
|
||||||
// Items is an ordered array of content items within the lesson
|
// Items is an ordered array of content items within the lesson
|
||||||
Items []Item `json:"items"`
|
Items []Item `json:"items"`
|
||||||
// Position stores the ordering information for the lesson
|
// Position stores the ordering information for the lesson.
|
||||||
|
// NOTE: Currently parsed but not used in export logic.
|
||||||
Position any `json:"position"`
|
Position any `json:"position"`
|
||||||
// Ready indicates whether the lesson is marked as complete
|
// Ready indicates whether the lesson is marked as complete
|
||||||
Ready bool `json:"ready"`
|
Ready bool `json:"ready"`
|
||||||
@@ -38,9 +39,11 @@ type Item struct {
|
|||||||
Variant string `json:"variant"`
|
Variant string `json:"variant"`
|
||||||
// Items contains the actual content elements (sub-items) of this item
|
// Items contains the actual content elements (sub-items) of this item
|
||||||
Items []SubItem `json:"items"`
|
Items []SubItem `json:"items"`
|
||||||
// Settings contains configuration options specific to this item type
|
// Settings contains configuration options specific to this item type.
|
||||||
|
// NOTE: Currently parsed but not used in export logic.
|
||||||
Settings any `json:"settings"`
|
Settings any `json:"settings"`
|
||||||
// Data contains additional structured data for the item
|
// Data contains additional structured data for the item.
|
||||||
|
// NOTE: Currently parsed but not used in export logic.
|
||||||
Data any `json:"data"`
|
Data any `json:"data"`
|
||||||
// Media contains any associated media for the item
|
// Media contains any associated media for the item
|
||||||
Media *Media `json:"media,omitempty"`
|
Media *Media `json:"media,omitempty"`
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kjanat/articulate-parser/internal/apperrors"
|
||||||
"github.com/kjanat/articulate-parser/internal/interfaces"
|
"github.com/kjanat/articulate-parser/internal/interfaces"
|
||||||
"github.com/kjanat/articulate-parser/internal/models"
|
"github.com/kjanat/articulate-parser/internal/models"
|
||||||
)
|
)
|
||||||
@@ -37,7 +38,7 @@ func NewApp(parser interfaces.CourseParser, exporterFactory interfaces.ExporterF
|
|||||||
func (a *App) ProcessCourseFromFile(ctx context.Context, filePath, format, outputPath string) error {
|
func (a *App) ProcessCourseFromFile(ctx context.Context, filePath, format, outputPath string) error {
|
||||||
// Check for cancellation before loading
|
// Check for cancellation before loading
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return fmt.Errorf("operation canceled: %w", err)
|
return fmt.Errorf("%w: %w", apperrors.ErrOperationCanceled, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
course, err := a.parser.LoadCourseFromFile(filePath)
|
course, err := a.parser.LoadCourseFromFile(filePath)
|
||||||
@@ -68,7 +69,7 @@ func (a *App) ProcessCourseFromURI(ctx context.Context, uri, format, outputPath
|
|||||||
func (a *App) exportCourse(ctx context.Context, course *models.Course, format, outputPath string) error {
|
func (a *App) exportCourse(ctx context.Context, course *models.Course, format, outputPath string) error {
|
||||||
// Check for cancellation before export
|
// Check for cancellation before export
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return fmt.Errorf("operation canceled: %w", err)
|
return fmt.Errorf("%w: %w", apperrors.ErrOperationCanceled, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
exporter, err := a.exporterFactory.CreateExporter(format)
|
exporter, err := a.exporterFactory.CreateExporter(format)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/kjanat/articulate-parser/internal/apperrors"
|
||||||
"github.com/kjanat/articulate-parser/internal/config"
|
"github.com/kjanat/articulate-parser/internal/config"
|
||||||
"github.com/kjanat/articulate-parser/internal/interfaces"
|
"github.com/kjanat/articulate-parser/internal/interfaces"
|
||||||
"github.com/kjanat/articulate-parser/internal/models"
|
"github.com/kjanat/articulate-parser/internal/models"
|
||||||
@@ -127,17 +128,17 @@ func (p *ArticulateParser) extractShareID(uri string) (string, error) {
|
|||||||
// Parse the URL to validate the domain
|
// Parse the URL to validate the domain
|
||||||
parsedURL, err := url.Parse(uri)
|
parsedURL, err := url.Parse(uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("invalid URI: %s", uri)
|
return "", fmt.Errorf("%w: %s", apperrors.ErrInvalidURI, uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that it's an Articulate Rise domain
|
// Validate that it's an Articulate Rise domain
|
||||||
if parsedURL.Host != riseHost {
|
if parsedURL.Host != riseHost {
|
||||||
return "", fmt.Errorf("invalid domain for Articulate Rise URI: %s", parsedURL.Host)
|
return "", fmt.Errorf("%w: %s", apperrors.ErrInvalidDomain, parsedURL.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
matches := shareIDRegex.FindStringSubmatch(uri)
|
matches := shareIDRegex.FindStringSubmatch(uri)
|
||||||
if len(matches) < 2 {
|
if len(matches) < 2 {
|
||||||
return "", fmt.Errorf("could not extract share ID from URI: %s", uri)
|
return "", fmt.Errorf("%w: %s", apperrors.ErrNoShareID, uri)
|
||||||
}
|
}
|
||||||
return matches[1], nil
|
return matches[1], nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user