mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-01-16 10:22:09 +01:00
chore(build): introduce go-task for project automation
Adds a comprehensive Taskfile.yml to centralize all project scripts for building, testing, linting, and Docker image management. The GitHub Actions CI workflow is refactored to utilize these `task` commands, resulting in a cleaner, more readable, and maintainable configuration. This approach ensures consistency between local development and CI environments.
This commit is contained in:
114
.github/workflows/ci.yml
vendored
114
.github/workflows/ci.yml
vendored
@ -37,47 +37,17 @@ jobs:
|
|||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Download dependencies with retry
|
- name: Install Task
|
||||||
run: |
|
uses: go-task/setup-task@v1
|
||||||
set -e
|
|
||||||
echo "Downloading Go dependencies..."
|
|
||||||
|
|
||||||
# Function to download with retry
|
- name: Show build info
|
||||||
download_with_retry() {
|
run: task info
|
||||||
local attempt=1
|
|
||||||
local max_attempts=3
|
|
||||||
|
|
||||||
while [ $attempt -le $max_attempts ]; do
|
- name: Download dependencies
|
||||||
echo "Attempt $attempt of $max_attempts"
|
run: task deps
|
||||||
|
|
||||||
if go mod download; then
|
|
||||||
echo "Download successful on attempt $attempt"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo "Download failed on attempt $attempt"
|
|
||||||
if [ $attempt -lt $max_attempts ]; then
|
|
||||||
echo "Cleaning cache and retrying..."
|
|
||||||
go clean -modcache
|
|
||||||
go clean -cache
|
|
||||||
sleep 2
|
|
||||||
fi
|
|
||||||
attempt=$((attempt + 1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "All download attempts failed"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Try download with retry logic
|
|
||||||
download_with_retry
|
|
||||||
|
|
||||||
echo "Verifying module dependencies..."
|
|
||||||
go mod verify
|
|
||||||
echo "Dependencies verified successfully"
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go build -v ./...
|
run: task build
|
||||||
|
|
||||||
- name: Run tests with enhanced reporting
|
- name: Run tests with enhanced reporting
|
||||||
id: test
|
id: test
|
||||||
@ -89,7 +59,7 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
echo "Running tests with coverage..."
|
echo "Running tests with coverage..."
|
||||||
go test -v -race -coverprofile=coverage.out ./... 2>&1 | tee test-output.log
|
task test:coverage 2>&1 | tee test-output.log
|
||||||
|
|
||||||
# Extract test results for summary
|
# Extract test results for summary
|
||||||
TEST_STATUS=$?
|
TEST_STATUS=$?
|
||||||
@ -151,9 +121,8 @@ jobs:
|
|||||||
- name: Generate coverage report
|
- name: Generate coverage report
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
if [ -f coverage.out ]; then
|
if [ -f coverage/coverage.out ]; then
|
||||||
go tool cover -html=coverage.out -o coverage.html
|
COVERAGE=$(go tool cover -func=coverage/coverage.out | grep total | awk '{print $3}')
|
||||||
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}')
|
|
||||||
|
|
||||||
echo "## 📊 Code Coverage (Go ${{ matrix.go }})" >> $GITHUB_STEP_SUMMARY
|
echo "## 📊 Code Coverage (Go ${{ matrix.go }})" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
@ -171,7 +140,7 @@ jobs:
|
|||||||
temp_coverage=$(mktemp)
|
temp_coverage=$(mktemp)
|
||||||
|
|
||||||
# Extract package-level coverage data
|
# Extract package-level coverage data
|
||||||
go tool cover -func=coverage.out | grep -v total | while read line; do
|
go tool cover -func=coverage/coverage.out | grep -v total | while read line; do
|
||||||
if [[ $line == *".go:"* ]]; then
|
if [[ $line == *".go:"* ]]; then
|
||||||
# Extract package path from file path (everything before the filename)
|
# Extract package path from file path (everything before the filename)
|
||||||
filepath=$(echo "$line" | awk '{print $1}')
|
filepath=$(echo "$line" | awk '{print $1}')
|
||||||
@ -217,16 +186,16 @@ jobs:
|
|||||||
name: test-results-go-${{ matrix.go }}
|
name: test-results-go-${{ matrix.go }}
|
||||||
path: |
|
path: |
|
||||||
test-output.log
|
test-output.log
|
||||||
coverage.out
|
coverage/
|
||||||
coverage.html
|
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: Run go vet
|
- name: Run linters
|
||||||
run: |
|
run: |
|
||||||
echo "## 🔍 Static Analysis (Go ${{ matrix.go }})" >> $GITHUB_STEP_SUMMARY
|
echo "## 🔍 Static Analysis (Go ${{ matrix.go }})" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
VET_OUTPUT=$(go vet ./... 2>&1 || echo "")
|
# Run go vet
|
||||||
|
VET_OUTPUT=$(task lint:vet 2>&1 || echo "")
|
||||||
VET_STATUS=$?
|
VET_STATUS=$?
|
||||||
|
|
||||||
if [ $VET_STATUS -eq 0 ]; then
|
if [ $VET_STATUS -eq 0 ]; then
|
||||||
@ -240,13 +209,11 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
exit $VET_STATUS
|
# Run go fmt check
|
||||||
|
FMT_OUTPUT=$(task lint:fmt 2>&1 || echo "")
|
||||||
|
FMT_STATUS=$?
|
||||||
|
|
||||||
- name: Run go fmt
|
if [ $FMT_STATUS -eq 0 ]; then
|
||||||
run: |
|
|
||||||
FMT_OUTPUT=$(gofmt -s -l . 2>&1 || echo "")
|
|
||||||
|
|
||||||
if [ -z "$FMT_OUTPUT" ]; then
|
|
||||||
echo "✅ **go fmt:** All files properly formatted" >> $GITHUB_STEP_SUMMARY
|
echo "✅ **go fmt:** All files properly formatted" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "❌ **go fmt:** Files need formatting" >> $GITHUB_STEP_SUMMARY
|
echo "❌ **go fmt:** Files need formatting" >> $GITHUB_STEP_SUMMARY
|
||||||
@ -254,7 +221,10 @@ jobs:
|
|||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "$FMT_OUTPUT" >> $GITHUB_STEP_SUMMARY
|
echo "$FMT_OUTPUT" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
fi
|
||||||
|
|
||||||
|
# Exit with error if any linter failed
|
||||||
|
if [ $VET_STATUS -ne 0 ] || [ $FMT_STATUS -ne 0 ]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@ -276,6 +246,7 @@ jobs:
|
|||||||
- 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
|
||||||
flags: Go ${{ matrix.go }}
|
flags: Go ${{ matrix.go }}
|
||||||
slug: kjanat/articulate-parser
|
slug: kjanat/articulate-parser
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
@ -297,40 +268,37 @@ jobs:
|
|||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
check-latest: true
|
||||||
|
|
||||||
|
- name: Install Task
|
||||||
|
uses: go-task/setup-task@v1
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Capture build date
|
- name: Build Docker image using Task
|
||||||
run: echo "BUILD_TIME=$(git log -1 --format=%cd --date=iso-strict)" >> $GITHUB_ENV
|
run: task docker:build
|
||||||
|
|
||||||
- name: Build Docker image (test)
|
- name: Test Docker image using Task
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: false
|
|
||||||
load: true
|
|
||||||
tags: test:latest
|
|
||||||
build-args: |
|
|
||||||
VERSION=test
|
|
||||||
BUILD_TIME=${{ env.BUILD_TIME }}
|
|
||||||
GIT_COMMIT=${{ github.sha }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
|
|
||||||
- name: Test Docker image
|
|
||||||
run: |
|
run: |
|
||||||
echo "## 🧪 Docker Image Tests" >> $GITHUB_STEP_SUMMARY
|
echo "## 🧪 Docker Image Tests" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Test that the image runs and shows help
|
# Run Task docker test
|
||||||
|
task docker:test
|
||||||
|
|
||||||
echo "**Testing help command:**" >> $GITHUB_STEP_SUMMARY
|
echo "**Testing help command:**" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
docker run --rm test:latest --help >> $GITHUB_STEP_SUMMARY
|
docker run --rm articulate-parser:latest --help >> $GITHUB_STEP_SUMMARY
|
||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Test image size
|
# Test image size
|
||||||
IMAGE_SIZE=$(docker image inspect test:latest --format='{{.Size}}' | numfmt --to=iec-i --suffix=B)
|
IMAGE_SIZE=$(docker image inspect articulate-parser:latest --format='{{.Size}}' | numfmt --to=iec-i --suffix=B)
|
||||||
echo "**Image size:** $IMAGE_SIZE" >> $GITHUB_STEP_SUMMARY
|
echo "**Image size:** $IMAGE_SIZE" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -69,3 +69,5 @@ main_coverage
|
|||||||
# Editors
|
# Editors
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
.task/
|
||||||
|
|||||||
@ -49,7 +49,7 @@ RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
|
|||||||
RUN file /app/articulate-parser || echo "file command not available"
|
RUN file /app/articulate-parser || echo "file command not available"
|
||||||
|
|
||||||
# Development stage - uses Alpine for shell access
|
# Development stage - uses Alpine for shell access
|
||||||
FROM alpine:3.22.1
|
FROM alpine:3
|
||||||
|
|
||||||
# Install minimal dependencies
|
# Install minimal dependencies
|
||||||
RUN apk add --no-cache ca-certificates tzdata
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|||||||
571
Taskfile.yml
Normal file
571
Taskfile.yml
Normal file
@ -0,0 +1,571 @@
|
|||||||
|
# yaml-language-server: $schema=https://taskfile.dev/schema.json
|
||||||
|
# Articulate Parser - Task Automation
|
||||||
|
# https://taskfile.dev
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
# Global output settings
|
||||||
|
output: prefixed
|
||||||
|
|
||||||
|
# Shell settings (only applied on Unix-like systems)
|
||||||
|
# Note: These are ignored on Windows where PowerShell/cmd is used
|
||||||
|
set: [errexit, pipefail]
|
||||||
|
shopt: [globstar]
|
||||||
|
|
||||||
|
# Watch mode interval
|
||||||
|
interval: 500ms
|
||||||
|
|
||||||
|
# Global variables
|
||||||
|
vars:
|
||||||
|
APP_NAME: articulate-parser
|
||||||
|
MAIN_FILE: main.go
|
||||||
|
OUTPUT_DIR: bin
|
||||||
|
COVERAGE_DIR: coverage
|
||||||
|
TEST_TIMEOUT: 5m
|
||||||
|
|
||||||
|
# Version info
|
||||||
|
VERSION:
|
||||||
|
sh: git describe --tags --always --dirty 2>/dev/null || echo "dev"
|
||||||
|
GIT_COMMIT:
|
||||||
|
sh: git rev-parse --short HEAD 2>/dev/null || echo "unknown"
|
||||||
|
BUILD_TIME: '{{now | date "2006-01-02T15:04:05Z07:00"}}'
|
||||||
|
|
||||||
|
# Go settings
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
GO_FLAGS: -v
|
||||||
|
LDFLAGS: >-
|
||||||
|
-s -w
|
||||||
|
-X github.com/kjanat/articulate-parser/internal/version.Version={{.VERSION}}
|
||||||
|
-X github.com/kjanat/articulate-parser/internal/version.BuildTime={{.BUILD_TIME}}
|
||||||
|
-X github.com/kjanat/articulate-parser/internal/version.GitCommit={{.GIT_COMMIT}}
|
||||||
|
|
||||||
|
# Platform detection (using Task built-in variables)
|
||||||
|
GOOS:
|
||||||
|
sh: go env GOOS
|
||||||
|
GOARCH:
|
||||||
|
sh: go env GOARCH
|
||||||
|
EXE_EXT: '{{if eq OS "windows"}}.exe{{end}}'
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: '{{.CGO_ENABLED}}'
|
||||||
|
GO111MODULE: on
|
||||||
|
|
||||||
|
# Load .env files if present
|
||||||
|
dotenv: ['.env', '.env.local']
|
||||||
|
|
||||||
|
# Task definitions
|
||||||
|
tasks:
|
||||||
|
# Default task - show help
|
||||||
|
default:
|
||||||
|
desc: Show available tasks
|
||||||
|
cmds:
|
||||||
|
- task --list
|
||||||
|
silent: true
|
||||||
|
|
||||||
|
# Development tasks
|
||||||
|
dev:
|
||||||
|
desc: Run the application in development mode (with hot reload)
|
||||||
|
aliases: [run, start]
|
||||||
|
interactive: true
|
||||||
|
watch: true
|
||||||
|
sources:
|
||||||
|
- '**/*.go'
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
cmds:
|
||||||
|
- task: build
|
||||||
|
- '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} --help'
|
||||||
|
|
||||||
|
# Build tasks
|
||||||
|
build:
|
||||||
|
desc: Build the application binary
|
||||||
|
aliases: [b]
|
||||||
|
deps: [clean-bin]
|
||||||
|
sources:
|
||||||
|
- '**/*.go'
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
generates:
|
||||||
|
- '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}}'
|
||||||
|
cmds:
|
||||||
|
- task: mkdir
|
||||||
|
vars: { DIR: '{{.OUTPUT_DIR}}' }
|
||||||
|
- go build {{.GO_FLAGS}} -ldflags="{{.LDFLAGS}}" -o {{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} {{.MAIN_FILE}}
|
||||||
|
method: checksum
|
||||||
|
|
||||||
|
build:all:
|
||||||
|
desc: Build binaries for all major platforms
|
||||||
|
aliases: [build-all, cross-compile]
|
||||||
|
deps: [clean-bin]
|
||||||
|
cmds:
|
||||||
|
- task: mkdir
|
||||||
|
vars: { DIR: '{{.OUTPUT_DIR}}' }
|
||||||
|
- for:
|
||||||
|
matrix:
|
||||||
|
GOOS: [linux, darwin, windows]
|
||||||
|
GOARCH: [amd64, arm64]
|
||||||
|
task: build:platform
|
||||||
|
vars:
|
||||||
|
TARGET_GOOS: '{{.ITEM.GOOS}}'
|
||||||
|
TARGET_GOARCH: '{{.ITEM.GOARCH}}'
|
||||||
|
- echo "Built binaries for all platforms in {{.OUTPUT_DIR}}/"
|
||||||
|
|
||||||
|
build:platform:
|
||||||
|
internal: true
|
||||||
|
vars:
|
||||||
|
TARGET_EXT: '{{if eq .TARGET_GOOS "windows"}}.exe{{end}}'
|
||||||
|
OUTPUT_FILE: '{{.OUTPUT_DIR}}/{{.APP_NAME}}-{{.TARGET_GOOS}}-{{.TARGET_GOARCH}}{{.TARGET_EXT}}'
|
||||||
|
env:
|
||||||
|
GOOS: '{{.TARGET_GOOS}}'
|
||||||
|
GOARCH: '{{.TARGET_GOARCH}}'
|
||||||
|
cmds:
|
||||||
|
- echo "Building {{.OUTPUT_FILE}}..."
|
||||||
|
- go build {{.GO_FLAGS}} -ldflags="{{.LDFLAGS}}" -o "{{.OUTPUT_FILE}}" {{.MAIN_FILE}}
|
||||||
|
|
||||||
|
# Install task
|
||||||
|
install:
|
||||||
|
desc: Install the binary to $GOPATH/bin
|
||||||
|
deps: [test]
|
||||||
|
cmds:
|
||||||
|
- go install -ldflags="{{.LDFLAGS}}" {{.MAIN_FILE}}
|
||||||
|
- echo "Installed {{.APP_NAME}} to $(go env GOPATH)/bin"
|
||||||
|
|
||||||
|
# Testing tasks
|
||||||
|
test:
|
||||||
|
desc: Run all tests
|
||||||
|
aliases: [t]
|
||||||
|
cmds:
|
||||||
|
- go test {{.GO_FLAGS}} -race -timeout {{.TEST_TIMEOUT}} ./...
|
||||||
|
|
||||||
|
test:coverage:
|
||||||
|
desc: Run tests with coverage report
|
||||||
|
aliases: [cover, cov]
|
||||||
|
deps: [clean-coverage]
|
||||||
|
cmds:
|
||||||
|
- task: mkdir
|
||||||
|
vars: { DIR: '{{.COVERAGE_DIR}}' }
|
||||||
|
- go test {{.GO_FLAGS}} -race -coverprofile={{.COVERAGE_DIR}}/coverage.out -covermode=atomic -timeout {{.TEST_TIMEOUT}} ./...
|
||||||
|
- go tool cover -html={{.COVERAGE_DIR}}/coverage.out -o {{.COVERAGE_DIR}}/coverage.html
|
||||||
|
- go tool cover -func={{.COVERAGE_DIR}}/coverage.out
|
||||||
|
- echo "Coverage report generated at {{.COVERAGE_DIR}}/coverage.html"
|
||||||
|
|
||||||
|
test:verbose:
|
||||||
|
desc: Run tests with verbose output
|
||||||
|
aliases: [tv]
|
||||||
|
cmds:
|
||||||
|
- go test -v -race -timeout {{.TEST_TIMEOUT}} ./...
|
||||||
|
|
||||||
|
test:watch:
|
||||||
|
desc: Run tests in watch mode
|
||||||
|
aliases: [tw]
|
||||||
|
watch: true
|
||||||
|
sources:
|
||||||
|
- '**/*.go'
|
||||||
|
cmds:
|
||||||
|
- task: test
|
||||||
|
|
||||||
|
test:bench:
|
||||||
|
desc: Run benchmark tests
|
||||||
|
aliases: [bench]
|
||||||
|
cmds:
|
||||||
|
- go test -bench=. -benchmem -timeout {{.TEST_TIMEOUT}} ./...
|
||||||
|
|
||||||
|
test:integration:
|
||||||
|
desc: Run integration tests
|
||||||
|
status:
|
||||||
|
- '{{if eq OS "windows"}}if not exist "main_test.go" exit 1{{else}}test ! -f "main_test.go"{{end}}'
|
||||||
|
cmds:
|
||||||
|
- go test -v -race -tags=integration -timeout {{.TEST_TIMEOUT}} ./...
|
||||||
|
|
||||||
|
# Code quality tasks
|
||||||
|
lint:
|
||||||
|
desc: Run all linters
|
||||||
|
aliases: [l]
|
||||||
|
cmds:
|
||||||
|
- task: lint:vet
|
||||||
|
- task: lint:fmt
|
||||||
|
- task: lint:staticcheck
|
||||||
|
|
||||||
|
lint:vet:
|
||||||
|
desc: Run go vet
|
||||||
|
cmds:
|
||||||
|
- go vet ./...
|
||||||
|
|
||||||
|
lint:fmt:
|
||||||
|
desc: Check code formatting
|
||||||
|
vars:
|
||||||
|
UNFORMATTED:
|
||||||
|
sh: gofmt -s -l .
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
{{if ne .UNFORMATTED ""}}
|
||||||
|
echo "❌ The following files need formatting:"
|
||||||
|
echo "{{.UNFORMATTED}}"
|
||||||
|
exit 1
|
||||||
|
{{else}}
|
||||||
|
echo "All files are properly formatted"
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
lint:staticcheck:
|
||||||
|
desc: Run staticcheck (install if needed)
|
||||||
|
vars:
|
||||||
|
HAS_STATICCHECK:
|
||||||
|
sh: '{{if eq OS "windows"}}where staticcheck 2>NUL{{else}}command -v staticcheck 2>/dev/null{{end}}'
|
||||||
|
cmds:
|
||||||
|
- '{{if eq .HAS_STATICCHECK ""}}echo "Installing staticcheck..." && go install honnef.co/go/tools/cmd/staticcheck@latest{{end}}'
|
||||||
|
- staticcheck ./...
|
||||||
|
ignore_error: true
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
desc: Format all Go files
|
||||||
|
aliases: [format]
|
||||||
|
cmds:
|
||||||
|
- gofmt -s -w .
|
||||||
|
- echo "Formatted all Go files"
|
||||||
|
|
||||||
|
modernize:
|
||||||
|
desc: Modernize Go code to use modern idioms
|
||||||
|
aliases: [modern]
|
||||||
|
cmds:
|
||||||
|
- go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
|
||||||
|
- echo "Code modernized"
|
||||||
|
|
||||||
|
# Dependency management
|
||||||
|
deps:
|
||||||
|
desc: Download and verify dependencies
|
||||||
|
aliases: [mod]
|
||||||
|
cmds:
|
||||||
|
- go mod download
|
||||||
|
- go mod verify
|
||||||
|
- echo "Dependencies downloaded and verified"
|
||||||
|
|
||||||
|
deps:tidy:
|
||||||
|
desc: Tidy go.mod and go.sum
|
||||||
|
aliases: [tidy]
|
||||||
|
cmds:
|
||||||
|
- go mod tidy
|
||||||
|
- echo "Dependencies tidied"
|
||||||
|
|
||||||
|
deps:update:
|
||||||
|
desc: Update all dependencies to latest versions
|
||||||
|
aliases: [update]
|
||||||
|
cmds:
|
||||||
|
- go get -u ./...
|
||||||
|
- go mod tidy
|
||||||
|
- echo "Dependencies updated"
|
||||||
|
|
||||||
|
deps:graph:
|
||||||
|
desc: Display dependency graph
|
||||||
|
cmds:
|
||||||
|
- go mod graph
|
||||||
|
|
||||||
|
# Docker tasks
|
||||||
|
docker:build:
|
||||||
|
desc: Build Docker image
|
||||||
|
aliases: [db]
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
docker build \
|
||||||
|
--build-arg VERSION={{.VERSION}} \
|
||||||
|
--build-arg BUILD_TIME={{.BUILD_TIME}} \
|
||||||
|
--build-arg GIT_COMMIT={{.GIT_COMMIT}} \
|
||||||
|
-t {{.APP_NAME}}:{{.VERSION}} \
|
||||||
|
-t {{.APP_NAME}}:latest \
|
||||||
|
.
|
||||||
|
- >
|
||||||
|
echo "Docker image built: {{.APP_NAME}}:{{.VERSION}}"
|
||||||
|
|
||||||
|
docker:build:dev:
|
||||||
|
desc: Build development Docker image
|
||||||
|
cmds:
|
||||||
|
- docker build -f Dockerfile.dev -t {{.APP_NAME}}:dev .
|
||||||
|
- >
|
||||||
|
echo "Development Docker image built: {{.APP_NAME}}:dev"
|
||||||
|
|
||||||
|
docker:run:
|
||||||
|
desc: Run Docker container
|
||||||
|
aliases: [dr]
|
||||||
|
deps: [docker:build]
|
||||||
|
cmds:
|
||||||
|
- docker run --rm {{.APP_NAME}}:{{.VERSION}} --help
|
||||||
|
|
||||||
|
docker:test:
|
||||||
|
desc: Test Docker image
|
||||||
|
deps: [docker:build]
|
||||||
|
cmds:
|
||||||
|
- docker run --rm {{.APP_NAME}}:{{.VERSION}} --version
|
||||||
|
- echo "Docker image tested successfully"
|
||||||
|
|
||||||
|
docker:compose:up:
|
||||||
|
desc: Start services with docker-compose
|
||||||
|
cmds:
|
||||||
|
- docker-compose up -d
|
||||||
|
|
||||||
|
docker:compose:down:
|
||||||
|
desc: Stop services with docker-compose
|
||||||
|
cmds:
|
||||||
|
- docker-compose down
|
||||||
|
|
||||||
|
# Cleanup tasks
|
||||||
|
clean:
|
||||||
|
desc: Clean all generated files
|
||||||
|
aliases: [c]
|
||||||
|
cmds:
|
||||||
|
- task: clean-bin
|
||||||
|
- task: clean-coverage
|
||||||
|
- task: clean-cache
|
||||||
|
- echo "All generated files cleaned"
|
||||||
|
|
||||||
|
clean-bin:
|
||||||
|
desc: Remove built binaries
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- task: rmdir
|
||||||
|
vars: { DIR: '{{.OUTPUT_DIR}}' }
|
||||||
|
|
||||||
|
clean-coverage:
|
||||||
|
desc: Remove coverage files
|
||||||
|
internal: true
|
||||||
|
cmds:
|
||||||
|
- task: rmdir
|
||||||
|
vars: { DIR: '{{.COVERAGE_DIR}}' }
|
||||||
|
|
||||||
|
clean-cache:
|
||||||
|
desc: Clean Go build and test cache
|
||||||
|
cmds:
|
||||||
|
- go clean -cache -testcache -modcache
|
||||||
|
- echo "Go caches cleaned"
|
||||||
|
|
||||||
|
# CI/CD tasks
|
||||||
|
ci:
|
||||||
|
desc: Run all CI checks (test, lint, build)
|
||||||
|
cmds:
|
||||||
|
- task: deps
|
||||||
|
- task: lint
|
||||||
|
- task: test:coverage
|
||||||
|
- task: build
|
||||||
|
- echo "All CI checks passed"
|
||||||
|
|
||||||
|
ci:local:
|
||||||
|
desc: Run CI checks locally with detailed output
|
||||||
|
cmds:
|
||||||
|
- echo "🔍 Running local CI checks..."
|
||||||
|
- echo ""
|
||||||
|
- echo "📦 Checking dependencies..."
|
||||||
|
- task: deps
|
||||||
|
- echo ""
|
||||||
|
- echo "🔧 Running linters..."
|
||||||
|
- task: lint
|
||||||
|
- echo ""
|
||||||
|
- echo "🧪 Running tests with coverage..."
|
||||||
|
- task: test:coverage
|
||||||
|
- echo ""
|
||||||
|
- echo "🏗️ Building application..."
|
||||||
|
- task: build:all
|
||||||
|
- echo ""
|
||||||
|
- echo "All CI checks completed successfully!"
|
||||||
|
|
||||||
|
# Release tasks
|
||||||
|
release:check:
|
||||||
|
desc: Check if ready for release
|
||||||
|
cmds:
|
||||||
|
- task: ci
|
||||||
|
- git diff --exit-code
|
||||||
|
- git diff --cached --exit-code
|
||||||
|
- echo "Ready for release"
|
||||||
|
|
||||||
|
release:tag:
|
||||||
|
desc: Tag a new release (requires VERSION env var)
|
||||||
|
requires:
|
||||||
|
vars: [VERSION]
|
||||||
|
preconditions:
|
||||||
|
- sh: 'git diff --exit-code'
|
||||||
|
msg: 'Working directory is not clean'
|
||||||
|
- sh: 'git diff --cached --exit-code'
|
||||||
|
msg: 'Staging area is not clean'
|
||||||
|
cmds:
|
||||||
|
- git tag -a v{{.VERSION}} -m "Release v{{.VERSION}}"
|
||||||
|
- echo "Tagged v{{.VERSION}}"
|
||||||
|
- >
|
||||||
|
echo "Push with: git push origin v{{.VERSION}}"
|
||||||
|
|
||||||
|
# Documentation tasks
|
||||||
|
docs:serve:
|
||||||
|
desc: Serve documentation locally
|
||||||
|
vars:
|
||||||
|
HAS_GODOC:
|
||||||
|
sh: '{{if eq OS "windows"}}where godoc 2>NUL{{else}}command -v godoc 2>/dev/null{{end}}'
|
||||||
|
cmds:
|
||||||
|
- '{{if eq .HAS_GODOC ""}}echo "Installing godoc..." && go install golang.org/x/tools/cmd/godoc@latest{{end}}'
|
||||||
|
- echo "📚 Serving documentation at http://localhost:6060"
|
||||||
|
- godoc -http=:6060
|
||||||
|
interactive: true
|
||||||
|
|
||||||
|
docs:coverage:
|
||||||
|
desc: Open coverage report in browser
|
||||||
|
deps: [test:coverage]
|
||||||
|
cmds:
|
||||||
|
- '{{if eq OS "darwin"}}open {{.COVERAGE_DIR}}/coverage.html{{else if eq OS "windows"}}start {{.COVERAGE_DIR}}/coverage.html{{else}}xdg-open {{.COVERAGE_DIR}}/coverage.html 2>/dev/null || echo "Please open {{.COVERAGE_DIR}}/coverage.html in your browser"{{end}}'
|
||||||
|
|
||||||
|
# Info tasks
|
||||||
|
info:
|
||||||
|
desc: Display build information
|
||||||
|
vars:
|
||||||
|
GO_VERSION:
|
||||||
|
sh: go version
|
||||||
|
cmds:
|
||||||
|
- task: info:print
|
||||||
|
silent: true
|
||||||
|
|
||||||
|
info:print:
|
||||||
|
internal: true
|
||||||
|
silent: true
|
||||||
|
vars:
|
||||||
|
GO_VERSION:
|
||||||
|
sh: go version
|
||||||
|
cmds:
|
||||||
|
- echo "Application Info:"
|
||||||
|
- echo " Name{{":"}} {{.APP_NAME}}"
|
||||||
|
- echo " Version{{":"}} {{.VERSION}}"
|
||||||
|
- echo " Git Commit{{":"}} {{.GIT_COMMIT}}"
|
||||||
|
- echo " Build Time{{":"}} {{.BUILD_TIME}}"
|
||||||
|
- echo ""
|
||||||
|
- echo "Go Environment{{":"}}"
|
||||||
|
- echo " Go Version{{":"}} {{.GO_VERSION}}"
|
||||||
|
- echo " GOOS{{":"}} {{.GOOS}}"
|
||||||
|
- echo " GOARCH{{":"}} {{.GOARCH}}"
|
||||||
|
- echo " CGO{{":"}} {{.CGO_ENABLED}}"
|
||||||
|
- echo ""
|
||||||
|
- echo "Paths{{":"}}"
|
||||||
|
- echo " Output Dir{{":"}} {{.OUTPUT_DIR}}"
|
||||||
|
- echo " Coverage{{":"}} {{.COVERAGE_DIR}}"
|
||||||
|
|
||||||
|
# Security tasks
|
||||||
|
security:check:
|
||||||
|
desc: Run security checks with gosec
|
||||||
|
vars:
|
||||||
|
HAS_GOSEC:
|
||||||
|
sh: '{{if eq OS "windows"}}where gosec 2>NUL{{else}}command -v gosec 2>/dev/null{{end}}'
|
||||||
|
cmds:
|
||||||
|
- '{{if eq .HAS_GOSEC ""}}echo "Installing gosec..." && go install github.com/securego/gosec/v2/cmd/gosec@latest{{end}}'
|
||||||
|
- gosec ./...
|
||||||
|
ignore_error: true
|
||||||
|
|
||||||
|
security:audit:
|
||||||
|
desc: Audit dependencies for vulnerabilities
|
||||||
|
vars:
|
||||||
|
HAS_GOVULNCHECK:
|
||||||
|
sh: '{{if eq OS "windows"}}where govulncheck 2>NUL{{else}}command -v govulncheck 2>/dev/null{{end}}'
|
||||||
|
cmds:
|
||||||
|
- '{{if eq .HAS_GOVULNCHECK ""}}echo "Installing govulncheck..." && go install golang.org/x/vuln/cmd/govulncheck@latest{{end}}'
|
||||||
|
- govulncheck ./...
|
||||||
|
|
||||||
|
# Example/Demo tasks
|
||||||
|
demo:markdown:
|
||||||
|
desc: Demo - Convert sample to Markdown
|
||||||
|
status:
|
||||||
|
- '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}'
|
||||||
|
deps: [build]
|
||||||
|
cmds:
|
||||||
|
- '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json md output-demo.md'
|
||||||
|
- echo "Demo Markdown created{{:}} output-demo.md"
|
||||||
|
- defer:
|
||||||
|
task: rmfile
|
||||||
|
vars: { FILE: 'output-demo.md' }
|
||||||
|
|
||||||
|
demo:html:
|
||||||
|
desc: Demo - Convert sample to HTML
|
||||||
|
status:
|
||||||
|
- '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}'
|
||||||
|
deps: [build]
|
||||||
|
cmds:
|
||||||
|
- '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json html output-demo.html'
|
||||||
|
- echo "Demo HTML created{{:}} output-demo.html"
|
||||||
|
- defer:
|
||||||
|
task: rmfile
|
||||||
|
vars: { FILE: 'output-demo.html' }
|
||||||
|
|
||||||
|
demo:docx:
|
||||||
|
desc: Demo - Convert sample to DOCX
|
||||||
|
status:
|
||||||
|
- '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}'
|
||||||
|
deps: [build]
|
||||||
|
cmds:
|
||||||
|
- '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json docx output-demo.docx'
|
||||||
|
- echo "Demo DOCX created{{:}} output-demo.docx"
|
||||||
|
- defer:
|
||||||
|
task: rmfile
|
||||||
|
vars: { FILE: 'output-demo.docx' }
|
||||||
|
|
||||||
|
# Performance profiling
|
||||||
|
profile:cpu:
|
||||||
|
desc: Run CPU profiling
|
||||||
|
cmds:
|
||||||
|
- go test -cpuprofile=cpu.prof -bench=. ./...
|
||||||
|
- go tool pprof -http=:8080 cpu.prof
|
||||||
|
- defer:
|
||||||
|
task: rmfile
|
||||||
|
vars: { FILE: 'cpu.prof' }
|
||||||
|
|
||||||
|
profile:mem:
|
||||||
|
desc: Run memory profiling
|
||||||
|
cmds:
|
||||||
|
- go test -memprofile=mem.prof -bench=. ./...
|
||||||
|
- go tool pprof -http=:8080 mem.prof
|
||||||
|
- defer:
|
||||||
|
task: rmfile
|
||||||
|
vars: { FILE: 'mem.prof' }
|
||||||
|
|
||||||
|
# Git hooks
|
||||||
|
hooks:install:
|
||||||
|
desc: Install git hooks
|
||||||
|
cmds:
|
||||||
|
- task: mkdir
|
||||||
|
vars: { DIR: '.git/hooks' }
|
||||||
|
- '{{if eq OS "windows"}}echo "#!/bin/sh" > .git/hooks/pre-commit && echo "task lint:fmt" >> .git/hooks/pre-commit{{else}}cat > .git/hooks/pre-commit << ''EOF''{{printf "\n"}}#!/bin/sh{{printf "\n"}}task lint:fmt{{printf "\n"}}EOF{{printf "\n"}}chmod +x .git/hooks/pre-commit{{end}}'
|
||||||
|
- echo "Git hooks installed"
|
||||||
|
|
||||||
|
# Quick shortcuts
|
||||||
|
qa:
|
||||||
|
desc: Quick quality assurance (fmt + lint + test)
|
||||||
|
aliases: [q, quick]
|
||||||
|
cmds:
|
||||||
|
- task: fmt
|
||||||
|
- task: lint
|
||||||
|
- task: test
|
||||||
|
- echo "Quick QA passed"
|
||||||
|
|
||||||
|
all:
|
||||||
|
desc: Build everything (clean + deps + test + build:all + docker:build)
|
||||||
|
cmds:
|
||||||
|
- task: clean
|
||||||
|
- task: deps:tidy
|
||||||
|
- task: test:coverage
|
||||||
|
- task: build:all
|
||||||
|
- task: docker:build
|
||||||
|
- echo "Full build completed!"
|
||||||
|
|
||||||
|
# Cross-platform helper tasks
|
||||||
|
mkdir:
|
||||||
|
internal: true
|
||||||
|
requires:
|
||||||
|
vars: [DIR]
|
||||||
|
cmds:
|
||||||
|
- '{{if eq OS "windows"}}powershell -Command "New-Item -ItemType Directory -Force -Path ''{{.DIR}}'' | Out-Null"{{else}}mkdir -p "{{.DIR}}"{{end}}'
|
||||||
|
silent: true
|
||||||
|
|
||||||
|
rmdir:
|
||||||
|
internal: true
|
||||||
|
requires:
|
||||||
|
vars: [DIR]
|
||||||
|
cmds:
|
||||||
|
- '{{if eq OS "windows"}}powershell -Command "if (Test-Path ''{{.DIR}}'') { Remove-Item -Recurse -Force ''{{.DIR}}'' }"{{else}}rm -rf "{{.DIR}}" 2>/dev/null || true{{end}}'
|
||||||
|
silent: true
|
||||||
|
|
||||||
|
rmfile:
|
||||||
|
internal: true
|
||||||
|
requires:
|
||||||
|
vars: [FILE]
|
||||||
|
cmds:
|
||||||
|
- '{{if eq OS "windows"}}powershell -Command "if (Test-Path ''{{.FILE}}'') { Remove-Item -Force ''{{.FILE}}'' }"{{else}}rm -f "{{.FILE}}"{{end}}'
|
||||||
|
silent: true
|
||||||
2
go.sum
2
go.sum
@ -2,7 +2,5 @@ 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.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w=
|
|
||||||
golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g=
|
|
||||||
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
|
||||||
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
|
||||||
|
|||||||
@ -615,8 +615,7 @@ func BenchmarkDocxExporter_Export(b *testing.B) {
|
|||||||
// Create temporary directory
|
// Create temporary directory
|
||||||
tempDir := b.TempDir()
|
tempDir := b.TempDir()
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
outputPath := filepath.Join(tempDir, "benchmark-course.docx")
|
outputPath := filepath.Join(tempDir, "benchmark-course.docx")
|
||||||
_ = exporter.Export(course, outputPath)
|
_ = exporter.Export(course, outputPath)
|
||||||
// Clean up for next iteration
|
// Clean up for next iteration
|
||||||
@ -641,7 +640,7 @@ func BenchmarkDocxExporter_ComplexCourse(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fill with test data
|
// Fill with test data
|
||||||
for i := 0; i < 10; i++ {
|
for i := range 10 {
|
||||||
lesson := models.Lesson{
|
lesson := models.Lesson{
|
||||||
ID: "lesson-" + string(rune(i)),
|
ID: "lesson-" + string(rune(i)),
|
||||||
Title: "Lesson " + string(rune(i)),
|
Title: "Lesson " + string(rune(i)),
|
||||||
@ -649,13 +648,13 @@ func BenchmarkDocxExporter_ComplexCourse(b *testing.B) {
|
|||||||
Items: make([]models.Item, 5), // 5 items per lesson
|
Items: make([]models.Item, 5), // 5 items per lesson
|
||||||
}
|
}
|
||||||
|
|
||||||
for j := 0; j < 5; j++ {
|
for j := range 5 {
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Items: make([]models.SubItem, 3), // 3 sub-items per item
|
Items: make([]models.SubItem, 3), // 3 sub-items per item
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := 0; k < 3; k++ {
|
for k := range 3 {
|
||||||
item.Items[k] = models.SubItem{
|
item.Items[k] = models.SubItem{
|
||||||
Heading: "<h3>Heading " + string(rune(k)) + "</h3>",
|
Heading: "<h3>Heading " + string(rune(k)) + "</h3>",
|
||||||
Paragraph: "<p>Paragraph content with <strong>formatting</strong> for performance testing.</p>",
|
Paragraph: "<p>Paragraph content with <strong>formatting</strong> for performance testing.</p>",
|
||||||
@ -670,8 +669,7 @@ func BenchmarkDocxExporter_ComplexCourse(b *testing.B) {
|
|||||||
|
|
||||||
tempDir := b.TempDir()
|
tempDir := b.TempDir()
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
outputPath := filepath.Join(tempDir, "benchmark-complex.docx")
|
outputPath := filepath.Join(tempDir, "benchmark-complex.docx")
|
||||||
_ = exporter.Export(course, outputPath)
|
_ = exporter.Export(course, outputPath)
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
|
|||||||
@ -449,8 +449,7 @@ func BenchmarkFactory_CreateExporter(b *testing.B) {
|
|||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
factory := NewFactory(htmlCleaner)
|
factory := NewFactory(htmlCleaner)
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = factory.CreateExporter("markdown")
|
_, _ = factory.CreateExporter("markdown")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -460,8 +459,7 @@ func BenchmarkFactory_CreateExporter_Docx(b *testing.B) {
|
|||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
factory := NewFactory(htmlCleaner)
|
factory := NewFactory(htmlCleaner)
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = factory.CreateExporter("docx")
|
_, _ = factory.CreateExporter("docx")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -471,8 +469,7 @@ func BenchmarkFactory_GetSupportedFormats(b *testing.B) {
|
|||||||
htmlCleaner := services.NewHTMLCleaner()
|
htmlCleaner := services.NewHTMLCleaner()
|
||||||
factory := NewFactory(htmlCleaner)
|
factory := NewFactory(htmlCleaner)
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = factory.GetSupportedFormats()
|
_ = factory.GetSupportedFormats()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -841,8 +841,7 @@ func BenchmarkHTMLExporter_Export(b *testing.B) {
|
|||||||
// Create temporary directory
|
// Create temporary directory
|
||||||
tempDir := b.TempDir()
|
tempDir := b.TempDir()
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
outputPath := filepath.Join(tempDir, "benchmark-course.html")
|
outputPath := filepath.Join(tempDir, "benchmark-course.html")
|
||||||
_ = exporter.Export(course, outputPath)
|
_ = exporter.Export(course, outputPath)
|
||||||
// Clean up for next iteration
|
// Clean up for next iteration
|
||||||
@ -865,8 +864,7 @@ func BenchmarkHTMLExporter_ProcessTextItem(b *testing.B) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
exporter.processTextItem(&buf, item)
|
exporter.processTextItem(&buf, item)
|
||||||
}
|
}
|
||||||
@ -889,7 +887,7 @@ func BenchmarkHTMLExporter_ComplexCourse(b *testing.B) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fill with test data
|
// Fill with test data
|
||||||
for i := 0; i < 10; i++ {
|
for i := range 10 {
|
||||||
lesson := models.Lesson{
|
lesson := models.Lesson{
|
||||||
ID: "lesson-" + string(rune(i)),
|
ID: "lesson-" + string(rune(i)),
|
||||||
Title: "Lesson " + string(rune(i)),
|
Title: "Lesson " + string(rune(i)),
|
||||||
@ -897,13 +895,13 @@ func BenchmarkHTMLExporter_ComplexCourse(b *testing.B) {
|
|||||||
Items: make([]models.Item, 5), // 5 items per lesson
|
Items: make([]models.Item, 5), // 5 items per lesson
|
||||||
}
|
}
|
||||||
|
|
||||||
for j := 0; j < 5; j++ {
|
for j := range 5 {
|
||||||
item := models.Item{
|
item := models.Item{
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Items: make([]models.SubItem, 3), // 3 sub-items per item
|
Items: make([]models.SubItem, 3), // 3 sub-items per item
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := 0; k < 3; k++ {
|
for k := range 3 {
|
||||||
item.Items[k] = models.SubItem{
|
item.Items[k] = models.SubItem{
|
||||||
Heading: "<h3>Heading " + string(rune(k)) + "</h3>",
|
Heading: "<h3>Heading " + string(rune(k)) + "</h3>",
|
||||||
Paragraph: "<p>Paragraph content with <strong>formatting</strong> for performance testing.</p>",
|
Paragraph: "<p>Paragraph content with <strong>formatting</strong> for performance testing.</p>",
|
||||||
@ -918,8 +916,7 @@ func BenchmarkHTMLExporter_ComplexCourse(b *testing.B) {
|
|||||||
|
|
||||||
tempDir := b.TempDir()
|
tempDir := b.TempDir()
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
outputPath := filepath.Join(tempDir, "benchmark-complex.html")
|
outputPath := filepath.Join(tempDir, "benchmark-complex.html")
|
||||||
_ = exporter.Export(course, outputPath)
|
_ = exporter.Export(course, outputPath)
|
||||||
os.Remove(outputPath)
|
os.Remove(outputPath)
|
||||||
|
|||||||
@ -661,8 +661,7 @@ func BenchmarkMarkdownExporter_Export(b *testing.B) {
|
|||||||
// Create temporary directory
|
// Create temporary directory
|
||||||
tempDir := b.TempDir()
|
tempDir := b.TempDir()
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
outputPath := filepath.Join(tempDir, "benchmark-course.md")
|
outputPath := filepath.Join(tempDir, "benchmark-course.md")
|
||||||
_ = exporter.Export(course, outputPath)
|
_ = exporter.Export(course, outputPath)
|
||||||
// Clean up for next iteration
|
// Clean up for next iteration
|
||||||
@ -685,8 +684,7 @@ func BenchmarkMarkdownExporter_ProcessTextItem(b *testing.B) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
exporter.processTextItem(&buf, item, "###")
|
exporter.processTextItem(&buf, item, "###")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ type Lesson struct {
|
|||||||
// 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
|
||||||
Position interface{} `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"`
|
||||||
// CreatedAt is the timestamp when the lesson was created
|
// CreatedAt is the timestamp when the lesson was created
|
||||||
@ -41,9 +41,9 @@ type Item struct {
|
|||||||
// 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
|
||||||
Settings interface{} `json:"settings"`
|
Settings any `json:"settings"`
|
||||||
// Data contains additional structured data for the item
|
// Data contains additional structured data for the item
|
||||||
Data interface{} `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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -133,7 +133,7 @@ func TestLesson_JSONMarshalUnmarshal(t *testing.T) {
|
|||||||
Ready: true,
|
Ready: true,
|
||||||
CreatedAt: "2023-06-01T12:00:00Z",
|
CreatedAt: "2023-06-01T12:00:00Z",
|
||||||
UpdatedAt: "2023-06-01T13:00:00Z",
|
UpdatedAt: "2023-06-01T13:00:00Z",
|
||||||
Position: map[string]interface{}{"x": 1, "y": 2},
|
Position: map[string]any{"x": 1, "y": 2},
|
||||||
Items: []Item{
|
Items: []Item{
|
||||||
{
|
{
|
||||||
ID: "item-test",
|
ID: "item-test",
|
||||||
@ -154,8 +154,8 @@ func TestLesson_JSONMarshalUnmarshal(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Settings: map[string]interface{}{"autoplay": false},
|
Settings: map[string]any{"autoplay": false},
|
||||||
Data: map[string]interface{}{"metadata": "test"},
|
Data: map[string]any{"metadata": "test"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -197,11 +197,11 @@ func TestItem_JSONMarshalUnmarshal(t *testing.T) {
|
|||||||
Feedback: "Well done!",
|
Feedback: "Well done!",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Settings: map[string]interface{}{
|
Settings: map[string]any{
|
||||||
"allowRetry": true,
|
"allowRetry": true,
|
||||||
"showAnswer": true,
|
"showAnswer": true,
|
||||||
},
|
},
|
||||||
Data: map[string]interface{}{
|
Data: map[string]any{
|
||||||
"points": 10,
|
"points": 10,
|
||||||
"weight": 1.5,
|
"weight": 1.5,
|
||||||
},
|
},
|
||||||
@ -475,7 +475,7 @@ func TestLabelSet_JSONMarshalUnmarshal(t *testing.T) {
|
|||||||
func TestEmptyStructures(t *testing.T) {
|
func TestEmptyStructures(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
data interface{}
|
data any
|
||||||
}{
|
}{
|
||||||
{"Empty Course", Course{}},
|
{"Empty Course", Course{}},
|
||||||
{"Empty CourseInfo", CourseInfo{}},
|
{"Empty CourseInfo", CourseInfo{}},
|
||||||
@ -626,8 +626,7 @@ func BenchmarkCourse_JSONMarshal(b *testing.B) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = json.Marshal(course)
|
_, _ = json.Marshal(course)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -660,17 +659,16 @@ func BenchmarkCourse_JSONUnmarshal(b *testing.B) {
|
|||||||
|
|
||||||
jsonData, _ := json.Marshal(course)
|
jsonData, _ := json.Marshal(course)
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
var result Course
|
var result Course
|
||||||
_ = json.Unmarshal(jsonData, &result)
|
_ = json.Unmarshal(jsonData, &result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// compareMaps compares two interface{} values that should be maps
|
// compareMaps compares two interface{} values that should be maps
|
||||||
func compareMaps(original, unmarshaled interface{}) bool {
|
func compareMaps(original, unmarshaled any) bool {
|
||||||
origMap, origOk := original.(map[string]interface{})
|
origMap, origOk := original.(map[string]any)
|
||||||
unMap, unOk := unmarshaled.(map[string]interface{})
|
unMap, unOk := unmarshaled.(map[string]any)
|
||||||
|
|
||||||
if !origOk || !unOk {
|
if !origOk || !unOk {
|
||||||
// If not maps, use deep equal
|
// If not maps, use deep equal
|
||||||
|
|||||||
@ -168,7 +168,7 @@ func TestHTMLCleaner_CleanHTML_LargeContent(t *testing.T) {
|
|||||||
// Create a large HTML string
|
// Create a large HTML string
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
builder.WriteString("<html><body>")
|
builder.WriteString("<html><body>")
|
||||||
for i := 0; i < 1000; i++ {
|
for i := range 1000 {
|
||||||
builder.WriteString("<p>Paragraph ")
|
builder.WriteString("<p>Paragraph ")
|
||||||
builder.WriteString(string(rune('0' + i%10)))
|
builder.WriteString(string(rune('0' + i%10)))
|
||||||
builder.WriteString(" with some content & entities.</p>")
|
builder.WriteString(" with some content & entities.</p>")
|
||||||
@ -299,8 +299,7 @@ func BenchmarkHTMLCleaner_CleanHTML(b *testing.B) {
|
|||||||
cleaner := NewHTMLCleaner()
|
cleaner := NewHTMLCleaner()
|
||||||
input := "<div class=\"content\"><h1>Course Title</h1><p>This is a <em>great</em> course about & HTML entities like and "quotes".</p><ul><li>Item 1</li><li>Item 2</li></ul></div>"
|
input := "<div class=\"content\"><h1>Course Title</h1><p>This is a <em>great</em> course about & HTML entities like and "quotes".</p><ul><li>Item 1</li><li>Item 2</li></ul></div>"
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
cleaner.CleanHTML(input)
|
cleaner.CleanHTML(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,15 +310,14 @@ func BenchmarkHTMLCleaner_CleanHTML_Large(b *testing.B) {
|
|||||||
|
|
||||||
// Create a large HTML string
|
// Create a large HTML string
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
for i := 0; i < 100; i++ {
|
for i := range 100 {
|
||||||
builder.WriteString("<p>Paragraph ")
|
builder.WriteString("<p>Paragraph ")
|
||||||
builder.WriteString(string(rune('0' + i%10)))
|
builder.WriteString(string(rune('0' + i%10)))
|
||||||
builder.WriteString(" with some content & entities <test>.</p>")
|
builder.WriteString(" with some content & entities <test>.</p>")
|
||||||
}
|
}
|
||||||
input := builder.String()
|
input := builder.String()
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
cleaner.CleanHTML(input)
|
cleaner.CleanHTML(input)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -420,8 +420,7 @@ func BenchmarkExtractShareID(b *testing.B) {
|
|||||||
parser := &ArticulateParser{}
|
parser := &ArticulateParser{}
|
||||||
uri := "https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/"
|
uri := "https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/"
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, _ = parser.extractShareID(uri)
|
_, _ = parser.extractShareID(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,8 +432,7 @@ func BenchmarkBuildAPIURL(b *testing.B) {
|
|||||||
}
|
}
|
||||||
shareID := "N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO"
|
shareID := "N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO"
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = parser.buildAPIURL(shareID)
|
_ = parser.buildAPIURL(shareID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,8 +89,7 @@ func TestIsURI(t *testing.T) {
|
|||||||
func BenchmarkIsURI(b *testing.B) {
|
func BenchmarkIsURI(b *testing.B) {
|
||||||
testStr := "https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/"
|
testStr := "https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/"
|
||||||
|
|
||||||
b.ResetTimer()
|
for b.Loop() {
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
isURI(testStr)
|
isURI(testStr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user