mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-01-16 10:22:09 +01:00
Compare commits
7 Commits
v0.3.1
...
c4086a832f
| Author | SHA1 | Date | |
|---|---|---|---|
| c4086a832f | |||
| 903ee92e4c | |||
|
9c51c0d9e3
|
|||
|
ec5c8c099c
|
|||
| 9eaf7dfcf2 | |||
| b7f23b2387 | |||
| a0003983c4 |
68
.dockerignore
Normal file
68
.dockerignore
Normal file
@ -0,0 +1,68 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# CI/CD
|
||||
.github
|
||||
.codecov.yml
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
*.md
|
||||
docs/
|
||||
|
||||
# Build artifacts
|
||||
build/
|
||||
dist/
|
||||
*.exe
|
||||
*.tar.gz
|
||||
*.zip
|
||||
|
||||
# Test files
|
||||
*_test.go
|
||||
test_*.go
|
||||
test/
|
||||
coverage.out
|
||||
coverage.html
|
||||
*.log
|
||||
|
||||
# Development
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS specific
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Output and temporary files
|
||||
output/
|
||||
tmp/
|
||||
temp/
|
||||
|
||||
# Node.js (if any)
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# Python (if any)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
|
||||
# Scripts (build scripts not needed in container)
|
||||
scripts/
|
||||
|
||||
# Sample files
|
||||
articulate-sample.json
|
||||
test_input.json
|
||||
|
||||
# License
|
||||
LICENSE
|
||||
50
.github/dependabot.yml
vendored
50
.github/dependabot.yml
vendored
@ -11,11 +11,57 @@ updates:
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- 'dependencies'
|
||||
- 'github-actions'
|
||||
- 'dependencies/github-actions'
|
||||
commit-message:
|
||||
prefix: 'ci'
|
||||
include: 'scope'
|
||||
|
||||
# Check for updates to Docker
|
||||
- package-ecosystem: 'docker'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
day: 'monday'
|
||||
time: '07:00'
|
||||
timezone: 'Europe/Amsterdam'
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- 'dependencies'
|
||||
- 'dependencies/docker'
|
||||
commit-message:
|
||||
prefix: 'docker'
|
||||
include: 'scope'
|
||||
groups:
|
||||
docker-images:
|
||||
patterns:
|
||||
- '*'
|
||||
update-types:
|
||||
- 'minor'
|
||||
- 'patch'
|
||||
|
||||
# Check for updates to Docker Compose
|
||||
- package-ecosystem: 'docker-compose'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
day: 'monday'
|
||||
time: '07:00'
|
||||
timezone: 'Europe/Amsterdam'
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- 'dependencies'
|
||||
- 'dependencies/docker-compose'
|
||||
commit-message:
|
||||
prefix: 'docker'
|
||||
include: 'scope'
|
||||
groups:
|
||||
docker-compose:
|
||||
patterns:
|
||||
- '*'
|
||||
update-types:
|
||||
- 'minor'
|
||||
- 'patch'
|
||||
|
||||
# Check for updates to Go modules
|
||||
- package-ecosystem: 'gomod'
|
||||
directory: '/'
|
||||
@ -27,7 +73,7 @@ updates:
|
||||
open-pull-requests-limit: 10
|
||||
labels:
|
||||
- 'dependencies'
|
||||
- 'go'
|
||||
- 'dependencies/go'
|
||||
commit-message:
|
||||
prefix: 'deps'
|
||||
include: 'scope'
|
||||
|
||||
360
.github/workflows/ci.yml
vendored
360
.github/workflows/ci.yml
vendored
@ -2,11 +2,17 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master", "develop" ]
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
branches: ['master', 'develop']
|
||||
pull_request:
|
||||
branches: [ "master", "develop" ]
|
||||
branches: ['master', 'develop', 'feature/*']
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@ -30,13 +36,45 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
check-latest: true
|
||||
cache-dependency-path: "**/*.sum"
|
||||
|
||||
- name: Download dependencies
|
||||
run: go mod download && echo "Download successful" || go mod tidy && echo "Tidy successful" || return 1
|
||||
- name: Download dependencies with retry
|
||||
run: |
|
||||
set -e
|
||||
echo "Downloading Go dependencies..."
|
||||
|
||||
- name: Verify dependencies
|
||||
run: go mod verify
|
||||
# Function to download with retry
|
||||
download_with_retry() {
|
||||
local attempt=1
|
||||
local max_attempts=3
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
echo "Attempt $attempt of $max_attempts"
|
||||
|
||||
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
|
||||
run: go build -v ./...
|
||||
@ -123,18 +161,47 @@ jobs:
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Add coverage by package
|
||||
echo "### 📋 Coverage by Package" >> $GITHUB_STEP_SUMMARY
|
||||
echo "<details>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "<summary>Click to expand 📋 Coverage by Package details</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | Coverage |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|---------|----------|" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Create temporary file for package coverage aggregation
|
||||
temp_coverage=$(mktemp)
|
||||
|
||||
# Extract package-level coverage data
|
||||
go tool cover -func=coverage.out | grep -v total | while read line; do
|
||||
if [[ $line == *".go:"* ]]; then
|
||||
pkg=$(echo $line | awk '{print $1}' | cut -d'/' -f1-3)
|
||||
coverage=$(echo $line | awk '{print $3}')
|
||||
echo "| $pkg | $coverage |" >> $GITHUB_STEP_SUMMARY
|
||||
# Extract package path from file path (everything before the filename)
|
||||
filepath=$(echo "$line" | awk '{print $1}')
|
||||
pkg_path=$(dirname "$filepath" | sed 's|github.com/kjanat/articulate-parser/||' | sed 's|^\./||')
|
||||
coverage=$(echo "$line" | awk '{print $3}' | sed 's/%//')
|
||||
|
||||
# Use root package if no subdirectory
|
||||
if [[ "$pkg_path" == "." || "$pkg_path" == "" ]]; then
|
||||
pkg_path="root"
|
||||
fi
|
||||
done | sort -u
|
||||
|
||||
echo "$pkg_path $coverage" >> "$temp_coverage"
|
||||
fi
|
||||
done
|
||||
|
||||
# Aggregate coverage by package (average)
|
||||
awk '{
|
||||
packages[$1] += $2;
|
||||
counts[$1]++
|
||||
}
|
||||
END {
|
||||
for (pkg in packages) {
|
||||
avg = packages[pkg] / counts[pkg]
|
||||
printf "| %s | %.1f%% |\n", pkg, avg
|
||||
}
|
||||
}' $temp_coverage | sort >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
rm -f $temp_coverage
|
||||
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
@ -220,6 +287,53 @@ jobs:
|
||||
flags: Go ${{ matrix.go }}
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
docker-test:
|
||||
name: Docker Build Test
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Capture build date
|
||||
run: echo "BUILD_TIME=$(git log -1 --format=%cd --date=iso-strict)" >> $GITHUB_ENV
|
||||
|
||||
- name: Build Docker image (test)
|
||||
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: |
|
||||
echo "## 🧪 Docker Image Tests" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Test that the image runs and shows help
|
||||
echo "**Testing help command:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
docker run --rm test:latest --help >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Test image size
|
||||
IMAGE_SIZE=$(docker image inspect test:latest --format='{{.Size}}' | numfmt --to=iec-i --suffix=B)
|
||||
echo "**Image size:** $IMAGE_SIZE" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
dependency-review:
|
||||
name: Dependency Review
|
||||
runs-on: ubuntu-latest
|
||||
@ -236,126 +350,126 @@ jobs:
|
||||
fail-on-severity: moderate
|
||||
comment-summary-in-pr: always
|
||||
|
||||
release:
|
||||
name: Release
|
||||
|
||||
|
||||
docker:
|
||||
name: Docker Build & Push
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref_type == 'tag'
|
||||
permissions:
|
||||
contents: write
|
||||
needs: [ "test" ]
|
||||
contents: read
|
||||
packages: write
|
||||
needs: ['test']
|
||||
if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/heads/feature/docker'))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
check-latest: true
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Run tests
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.IMAGE_NAME }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Articulate Parser
|
||||
org.opencontainers.image.description=A powerful CLI tool to parse Articulate Rise courses and export them to multiple formats including Markdown HTML and DOCX. Supports media extraction content cleaning and batch processing for educational content conversion.
|
||||
org.opencontainers.image.vendor=kjanat
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.url=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/master/DOCKER.md
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
# Multi-architecture build - Docker automatically provides TARGETOS, TARGETARCH, etc.
|
||||
# Based on Go's supported platforms from 'go tool dist list'
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/arm/v7
|
||||
linux/386
|
||||
linux/ppc64le
|
||||
linux/s390x
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
annotations: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
VERSION=${{ github.ref_type == 'tag' && github.ref_name || github.sha }}
|
||||
BUILD_TIME=${{ github.event.head_commit.timestamp }}
|
||||
GIT_COMMIT=${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=A powerful CLI tool to parse Articulate Rise courses and export them to multiple formats including Markdown HTML and DOCX. Supports media extraction content cleaning and batch processing for educational content conversion.
|
||||
sbom: true
|
||||
provenance: true
|
||||
|
||||
- name: Generate Docker summary
|
||||
run: |
|
||||
echo "## 🚀 Release Tests" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## 🐳 Docker Build Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
go test -v ./... 2>&1 | tee release-test-output.log
|
||||
TEST_STATUS=$?
|
||||
|
||||
TOTAL_TESTS=$(grep -c "=== RUN" release-test-output.log || echo "0")
|
||||
PASSED_TESTS=$(grep -c "--- PASS:" release-test-output.log || echo "0")
|
||||
FAILED_TESTS=$(grep -c "--- FAIL:" release-test-output.log || echo "0")
|
||||
|
||||
echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Total Tests | $TOTAL_TESTS |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Passed | ✅ $PASSED_TESTS |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Failed | ❌ $FAILED_TESTS |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Status | $([ $TEST_STATUS -eq 0 ] && echo "✅ PASSED" || echo "❌ FAILED") |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Image:** \`ghcr.io/${{ github.repository }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
exit $TEST_STATUS
|
||||
|
||||
- name: Install UPX
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y upx
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
echo "## 🔨 Build Process" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Set the build time environment variable
|
||||
BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
# Add run permissions to the build script
|
||||
chmod +x ./scripts/build.sh
|
||||
|
||||
# Display help information for the build script
|
||||
./scripts/build.sh --help
|
||||
|
||||
echo "**Build Configuration:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Version: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Build Time: $BUILD_TIME" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Git Commit: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Build for all platforms
|
||||
./scripts/build.sh \
|
||||
--verbose \
|
||||
-ldflags "-s -w -X github.com/kjanat/articulate-parser/internal/version.Version=${{ github.ref_name }} -X github.com/kjanat/articulate-parser/internal/version.BuildTime=$BUILD_TIME -X github.com/kjanat/articulate-parser/internal/version.GitCommit=${{ github.sha }}"
|
||||
|
||||
- name: Compress binaries with UPX
|
||||
run: |
|
||||
echo "## 📦 Binary Compression" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "Compressing binaries with UPX..."
|
||||
cd build/
|
||||
|
||||
# Get original sizes
|
||||
echo "**Original sizes:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Tags built:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
ls -lah >> $GITHUB_STEP_SUMMARY
|
||||
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Features:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Platforms:** linux/amd64, linux/arm64, linux/arm/v7, linux/386, linux/ppc64le, linux/s390x" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Architecture optimization:** ✅ Native compilation for each platform" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Multi-arch image description:** ✅ Enabled" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **SBOM (Software Bill of Materials):** ✅ Generated" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Provenance attestation:** ✅ Generated" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Security scanning:** Ready for vulnerability analysis" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Usage:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
|
||||
echo "# Pull the image" >> $GITHUB_STEP_SUMMARY
|
||||
echo "docker pull ghcr.io/${{ github.repository }}:latest" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "# Run with help" >> $GITHUB_STEP_SUMMARY
|
||||
echo "docker run --rm ghcr.io/${{ github.repository }}:latest --help" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "# Process a local file (mount current directory)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "docker run --rm -v \$(pwd):/workspace ghcr.io/${{ github.repository }}:latest /workspace/input.json markdown /workspace/output.md" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Compress all binaries except Darwin (macOS) binaries as UPX doesn't work well with recent macOS versions
|
||||
for binary in articulate-parser-*; do
|
||||
if [[ "$binary" == *"darwin"* ]]; then
|
||||
echo "Skipping UPX compression for $binary (macOS compatibility)"
|
||||
else
|
||||
echo "Compressing $binary..."
|
||||
upx --best --lzma "$binary" || {
|
||||
echo "Warning: UPX compression failed for $binary, keeping original"
|
||||
}
|
||||
fi
|
||||
done
|
||||
|
||||
echo "**Final sizes:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
ls -lah >> $GITHUB_STEP_SUMMARY
|
||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v4.6.2
|
||||
with:
|
||||
name: build-artifacts
|
||||
path: build/
|
||||
if-no-files-found: ignore
|
||||
retention-days: 1
|
||||
compression-level: 9
|
||||
overwrite: true
|
||||
include-hidden-files: true
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/*
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
prerelease: ${{ startsWith(github.ref, 'refs/tags/v0.') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Security and quality analysis workflows
|
||||
codeql-analysis:
|
||||
name: CodeQL Analysis
|
||||
uses: ./.github/workflows/codeql.yml
|
||||
permissions:
|
||||
security-events: write
|
||||
packages: read
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
12
.github/workflows/codeql.yml
vendored
12
.github/workflows/codeql.yml
vendored
@ -11,13 +11,17 @@
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
# This workflow is configured to be called by other workflows for more controlled execution
|
||||
# This allows integration with the main CI pipeline and avoids redundant runs
|
||||
# To enable automatic execution, uncomment the triggers below:
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
workflow_call:
|
||||
schedule:
|
||||
- cron: '44 16 * * 6'
|
||||
# push:
|
||||
# branches: [ "master" ]
|
||||
# pull_request:
|
||||
# branches: [ "master" ]
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
|
||||
25
.github/workflows/dependency-review.yml
vendored
Normal file
25
.github/workflows/dependency-review.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Dependency Review
|
||||
|
||||
# This workflow is designed to be called by other workflows rather than triggered automatically
|
||||
# This allows for more controlled execution and integration with other CI/CD processes
|
||||
# To enable automatic execution on pull requests, uncomment the line below:
|
||||
# on: [pull_request]
|
||||
on: [workflow_call]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
# Required to post security advisories
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v4
|
||||
with:
|
||||
fail-on-severity: moderate
|
||||
comment-summary-in-pr: always
|
||||
156
.github/workflows/release.yml
vendored
Normal file
156
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,156 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
workflow_call:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Create Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
check-latest: true
|
||||
|
||||
- name: Run tests
|
||||
run: go test -v ./...
|
||||
|
||||
- name: Install UPX
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y upx
|
||||
|
||||
- name: Build binaries
|
||||
run: |
|
||||
# Set the build time environment variable using git commit timestamp
|
||||
BUILD_TIME=$(git log -1 --format=%cd --date=iso-strict)
|
||||
|
||||
# Add run permissions to the build script
|
||||
chmod +x ./scripts/build.sh
|
||||
|
||||
# Build for all platforms
|
||||
./scripts/build.sh \
|
||||
--verbose \
|
||||
-ldflags "-s -w -X github.com/kjanat/articulate-parser/internal/version.Version=${{ github.ref_name }} -X github.com/kjanat/articulate-parser/internal/version.BuildTime=$BUILD_TIME -X github.com/kjanat/articulate-parser/internal/version.GitCommit=${{ github.sha }}"
|
||||
|
||||
- name: Compress binaries
|
||||
run: |
|
||||
cd build/
|
||||
for binary in articulate-parser-*; do
|
||||
echo "Compressing $binary..."
|
||||
upx --best "$binary" || {
|
||||
echo "Warning: UPX compression failed for $binary, keeping original"
|
||||
}
|
||||
done
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
build/articulate-parser-linux-amd64
|
||||
build/articulate-parser-linux-arm64
|
||||
build/articulate-parser-windows-amd64.exe
|
||||
build/articulate-parser-windows-arm64.exe
|
||||
build/articulate-parser-darwin-amd64
|
||||
build/articulate-parser-darwin-arm64
|
||||
generate_release_notes: true
|
||||
draft: false
|
||||
# Mark pre-1.0 versions (v0.x.x) as prerelease since they are considered unstable
|
||||
# This helps users understand that these releases may have breaking changes
|
||||
prerelease: ${{ startsWith(github.ref, 'refs/tags/v0.') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
docker:
|
||||
name: Docker Build & Push
|
||||
runs-on: ubuntu-latest
|
||||
needs: ['release']
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.IMAGE_NAME }}
|
||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
labels: |
|
||||
org.opencontainers.image.title=Articulate Parser
|
||||
org.opencontainers.image.description=A powerful CLI tool to parse Articulate Rise courses and export them to multiple formats including Markdown HTML and DOCX. Supports media extraction content cleaning and batch processing for educational content conversion.
|
||||
org.opencontainers.image.vendor=kjanat
|
||||
org.opencontainers.image.licenses=MIT
|
||||
org.opencontainers.image.url=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.documentation=https://github.com/${{ github.repository }}/blob/master/DOCKER.md
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: |
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/arm/v7
|
||||
linux/386
|
||||
linux/ppc64le
|
||||
linux/s390x
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
annotations: ${{ steps.meta.outputs.labels }}
|
||||
build-args: |
|
||||
VERSION=${{ github.ref_name }}
|
||||
BUILD_TIME=${{ github.event.head_commit.timestamp }}
|
||||
GIT_COMMIT=${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
outputs: type=image,name=target,annotation-index.org.opencontainers.image.description=A powerful CLI tool to parse Articulate Rise courses and export them to multiple formats including Markdown HTML and DOCX. Supports media extraction content cleaning and batch processing for educational content conversion.
|
||||
sbom: true
|
||||
provenance: true
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -64,3 +64,8 @@ main_coverage
|
||||
*.test
|
||||
*.out
|
||||
/tmp/
|
||||
.github/copilot-instructions.md
|
||||
|
||||
# Editors
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
82
DOCKER.md
Normal file
82
DOCKER.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Articulate Parser - Docker
|
||||
|
||||
A powerful command-line tool for parsing and processing articulate data files, now available as a lightweight Docker container.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Pull from GitHub Container Registry
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/kjanat/articulate-parser:latest
|
||||
```
|
||||
|
||||
### Run with Articulate Rise URL
|
||||
|
||||
```bash
|
||||
docker run --rm -v $(pwd):/data ghcr.io/kjanat/articulate-parser:latest https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/ markdown /data/output.md
|
||||
```
|
||||
|
||||
### Run with local files
|
||||
|
||||
```bash
|
||||
docker run --rm -v $(pwd):/data ghcr.io/kjanat/articulate-parser:latest /data/input.json markdown /data/output.md
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic File Processing
|
||||
|
||||
```bash
|
||||
# Process from Articulate Rise URL
|
||||
docker run --rm -v $(pwd):/data ghcr.io/kjanat/articulate-parser:latest https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/ markdown /data/output.md
|
||||
|
||||
# Process a local JSON file
|
||||
docker run --rm -v $(pwd):/data ghcr.io/kjanat/articulate-parser:latest /data/document.json markdown /data/output.md
|
||||
|
||||
# Process with specific format and output
|
||||
docker run --rm -v $(pwd):/data ghcr.io/kjanat/articulate-parser:latest /data/input.json docx /data/output.docx
|
||||
```
|
||||
|
||||
### Display Help and Version
|
||||
|
||||
```bash
|
||||
# Show help information
|
||||
docker run --rm ghcr.io/kjanat/articulate-parser:latest --help
|
||||
|
||||
# Show version
|
||||
docker run --rm ghcr.io/kjanat/articulate-parser:latest --version
|
||||
```
|
||||
|
||||
## Available Tags
|
||||
|
||||
- `latest` - Latest stable release
|
||||
- `v1.x.x` - Specific version tags
|
||||
- `main` - Latest development build
|
||||
|
||||
## Image Details
|
||||
|
||||
- **Base Image**: `scratch` (minimal attack surface)
|
||||
- **Architecture**: Multi-arch support (amd64, arm64)
|
||||
- **Size**: < 10MB (optimized binary)
|
||||
- **Security**: Runs as non-root user
|
||||
- **Features**: SBOM and provenance attestation included
|
||||
|
||||
## Development
|
||||
|
||||
### Local Build
|
||||
|
||||
```bash
|
||||
docker build -t articulate-parser .
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
## Repository
|
||||
|
||||
- **Source**: [github.com/kjanat/articulate-parser](https://github.com/kjanat/articulate-parser)
|
||||
- **Issues**: [Report bugs or request features](https://github.com/kjanat/articulate-parser/issues)
|
||||
- **License**: See repository for license details
|
||||
78
Dockerfile
Normal file
78
Dockerfile
Normal file
@ -0,0 +1,78 @@
|
||||
# Build stage
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
# Install git and ca-certificates (needed for fetching dependencies and HTTPS)
|
||||
RUN apk add --no-cache git ca-certificates tzdata file
|
||||
|
||||
# Create a non-root user for the final stage
|
||||
RUN adduser -D -u 1000 appuser
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
# Disable CGO for a fully static binary
|
||||
# Use linker flags to reduce binary size and embed version info
|
||||
ARG VERSION=dev
|
||||
ARG BUILD_TIME
|
||||
ARG GIT_COMMIT
|
||||
# Docker buildx automatically provides these for multi-platform builds
|
||||
ARG BUILDPLATFORM
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
# Debug: Show build information
|
||||
RUN echo "Building for platform: $TARGETPLATFORM (OS: $TARGETOS, Arch: $TARGETARCH, Variant: $TARGETVARIANT)" \
|
||||
&& echo "Build platform: $BUILDPLATFORM" \
|
||||
&& echo "Go version: $(go version)"
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
|
||||
-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}" \
|
||||
-o articulate-parser \
|
||||
./main.go
|
||||
|
||||
# Verify the binary architecture
|
||||
RUN file /app/articulate-parser || echo "file command not available"
|
||||
|
||||
# Final stage - minimal runtime image
|
||||
FROM scratch
|
||||
|
||||
# Copy CA certificates for HTTPS requests
|
||||
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
|
||||
# Copy timezone data
|
||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||
|
||||
# Add a minimal /etc/passwd file to support non-root user
|
||||
COPY --from=builder /etc/passwd /etc/passwd
|
||||
|
||||
# Copy the binary
|
||||
COPY --from=builder /app/articulate-parser /articulate-parser
|
||||
|
||||
# Switch to non-root user (appuser with UID 1000)
|
||||
USER appuser
|
||||
|
||||
# Set the binary as entrypoint
|
||||
ENTRYPOINT ["/articulate-parser"]
|
||||
|
||||
# Default command shows help
|
||||
CMD ["--help"]
|
||||
|
||||
# Add labels for metadata
|
||||
LABEL org.opencontainers.image.title="Articulate Parser"
|
||||
LABEL org.opencontainers.image.description="A powerful CLI tool to parse Articulate Rise courses and export them to multiple formats (Markdown, HTML, DOCX). Supports media extraction, content cleaning, and batch processing for educational content conversion."
|
||||
LABEL org.opencontainers.image.vendor="kjanat"
|
||||
LABEL org.opencontainers.image.licenses="MIT"
|
||||
LABEL org.opencontainers.image.source="https://github.com/kjanat/articulate-parser"
|
||||
LABEL org.opencontainers.image.documentation="https://github.com/kjanat/articulate-parser/blob/master/DOCKER.md"
|
||||
78
Dockerfile.dev
Normal file
78
Dockerfile.dev
Normal file
@ -0,0 +1,78 @@
|
||||
# Development Dockerfile with shell access
|
||||
# Uses Alpine instead of scratch for debugging
|
||||
|
||||
# Build stage - same as production
|
||||
FROM golang:1.24-alpine AS builder
|
||||
|
||||
# Install git and ca-certificates (needed for fetching dependencies and HTTPS)
|
||||
RUN apk add --no-cache git ca-certificates tzdata file
|
||||
|
||||
# Create a non-root user
|
||||
RUN adduser -D -u 1000 appuser
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
# Disable CGO for a fully static binary
|
||||
# Use linker flags to reduce binary size and embed version info
|
||||
ARG VERSION=dev
|
||||
ARG BUILD_TIME
|
||||
ARG GIT_COMMIT
|
||||
# Docker buildx automatically provides these for multi-platform builds
|
||||
ARG BUILDPLATFORM
|
||||
ARG TARGETPLATFORM
|
||||
ARG TARGETOS
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
||||
# Debug: Show build information
|
||||
RUN echo "Building for platform: $TARGETPLATFORM (OS: $TARGETOS, Arch: $TARGETARCH, Variant: $TARGETVARIANT)" \
|
||||
&& echo "Build platform: $BUILDPLATFORM" \
|
||||
&& echo "Go version: $(go version)"
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build \
|
||||
-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}" \
|
||||
-o articulate-parser \
|
||||
./main.go
|
||||
|
||||
# Verify the binary architecture
|
||||
RUN file /app/articulate-parser || echo "file command not available"
|
||||
|
||||
# Development stage - uses Alpine for shell access
|
||||
FROM alpine:3.21.3
|
||||
|
||||
# Install minimal dependencies
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
# Copy the binary
|
||||
COPY --from=builder /app/articulate-parser /articulate-parser
|
||||
|
||||
# Copy the non-root user configuration
|
||||
COPY --from=builder /etc/passwd /etc/passwd
|
||||
|
||||
# Switch to non-root user
|
||||
USER appuser
|
||||
|
||||
# Set the binary as entrypoint
|
||||
ENTRYPOINT ["/articulate-parser"]
|
||||
|
||||
# Default command shows help
|
||||
CMD ["--help"]
|
||||
|
||||
# Add labels for metadata
|
||||
LABEL org.opencontainers.image.title="Articulate Parser (Dev)"
|
||||
LABEL org.opencontainers.image.description="Development version of Articulate Parser with shell access"
|
||||
LABEL org.opencontainers.image.vendor="kjanat"
|
||||
LABEL org.opencontainers.image.licenses="MIT"
|
||||
LABEL org.opencontainers.image.source="https://github.com/kjanat/articulate-parser"
|
||||
LABEL org.opencontainers.image.documentation="https://github.com/kjanat/articulate-parser/blob/master/DOCKER.md"
|
||||
130
README.md
130
README.md
@ -9,6 +9,8 @@ A Go-based parser that converts Articulate Rise e-learning content to various fo
|
||||
[][MIT License] <!-- [][Commits] -->
|
||||
[][Commits]
|
||||
[][Issues]
|
||||
[][Docker image] <!-- [][Docker image] -->
|
||||
[][Docker workflow]
|
||||
[][Build]
|
||||
[][Codecov]
|
||||
|
||||
@ -101,7 +103,7 @@ The system follows **Clean Architecture** principles with clear separation of co
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go, I don't know the version, but I use go1.24.2 right now, and it works, see the [CI][Build] workflow where it is tested.
|
||||
- Go, I don't know the version, but I have [][gomod] configured right now, and it works, see the [CI][Build] workflow where it is tested.
|
||||
|
||||
### Install from source
|
||||
|
||||
@ -200,6 +202,130 @@ Then run:
|
||||
./articulate-parser input.json md output.md
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
The application is available as a Docker image from GitHub Container Registry.
|
||||
|
||||
### 🐳 Docker Image Information
|
||||
|
||||
- **Registry**: `ghcr.io/kjanat/articulate-parser`
|
||||
- **Platforms**: linux/amd64, linux/arm64
|
||||
- **Base Image**: Scratch (minimal footprint)
|
||||
- **Size**: ~15-20MB compressed
|
||||
|
||||
### Quick Start
|
||||
|
||||
```bash
|
||||
# Pull the latest image
|
||||
docker pull ghcr.io/kjanat/articulate-parser:latest
|
||||
|
||||
# Show help
|
||||
docker run --rm ghcr.io/kjanat/articulate-parser:latest --help
|
||||
```
|
||||
|
||||
### Available Tags
|
||||
|
||||
| Tag | Description | Use Case |
|
||||
|-----|-------------|----------|
|
||||
| `latest` | Latest stable release from master branch | Production use |
|
||||
| `edge` | Latest development build from master branch | Testing new features |
|
||||
| `v1.x.x` | Specific version releases | Production pinning |
|
||||
| `develop` | Development branch builds | Development/testing |
|
||||
| `feature/docker-ghcr` | Feature branch builds | Feature testing |
|
||||
| `master` | Latest master branch build | Continuous integration |
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Process a local file
|
||||
|
||||
```bash
|
||||
# Mount current directory and process a local JSON file
|
||||
docker run --rm -v $(pwd):/workspace \
|
||||
ghcr.io/kjanat/articulate-parser:latest \
|
||||
/workspace/input.json markdown /workspace/output.md
|
||||
```
|
||||
|
||||
#### Process from URL
|
||||
|
||||
```bash
|
||||
# Mount output directory and process from Articulate Rise URL
|
||||
docker run --rm -v $(pwd):/workspace \
|
||||
ghcr.io/kjanat/articulate-parser:latest \
|
||||
"https://rise.articulate.com/share/xyz" docx /workspace/output.docx
|
||||
```
|
||||
|
||||
#### Export to different formats
|
||||
|
||||
```bash
|
||||
# Export to HTML
|
||||
docker run --rm -v $(pwd):/workspace \
|
||||
ghcr.io/kjanat/articulate-parser:latest \
|
||||
/workspace/course.json html /workspace/course.html
|
||||
|
||||
# Export to Word Document
|
||||
docker run --rm -v $(pwd):/workspace \
|
||||
ghcr.io/kjanat/articulate-parser:latest \
|
||||
/workspace/course.json docx /workspace/course.docx
|
||||
|
||||
# Export to Markdown
|
||||
docker run --rm -v $(pwd):/workspace \
|
||||
ghcr.io/kjanat/articulate-parser:latest \
|
||||
/workspace/course.json md /workspace/course.md
|
||||
```
|
||||
|
||||
#### Batch Processing
|
||||
|
||||
```bash
|
||||
# Process multiple files in a directory
|
||||
docker run --rm -v $(pwd):/workspace \
|
||||
ghcr.io/kjanat/articulate-parser:latest \
|
||||
bash -c "for file in /workspace/*.json; do
|
||||
/articulate-parser \"\$file\" md \"\${file%.json}.md\"
|
||||
done"
|
||||
```
|
||||
|
||||
### Docker Compose
|
||||
|
||||
For local development, you can use the provided `docker-compose.yml`:
|
||||
|
||||
```bash
|
||||
# Build and run with default help command
|
||||
docker-compose up articulate-parser
|
||||
|
||||
# Process files using mounted volumes
|
||||
docker-compose up parser-with-files
|
||||
```
|
||||
|
||||
### Building Locally
|
||||
|
||||
```bash
|
||||
# Build the Docker image locally
|
||||
docker build -t articulate-parser:local .
|
||||
|
||||
# Run the local image
|
||||
docker run --rm articulate-parser:local --help
|
||||
|
||||
# Build with specific version
|
||||
docker build --build-arg VERSION=local --build-arg BUILD_TIME=$(date -u +%Y-%m-%dT%H:%M:%SZ) -t articulate-parser:local .
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
The Docker image supports the following build-time arguments:
|
||||
|
||||
| Argument | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `VERSION` | Version string embedded in the binary | `dev` |
|
||||
| `BUILD_TIME` | Build timestamp | Current time |
|
||||
| `GIT_COMMIT` | Git commit hash | Current commit |
|
||||
|
||||
### Docker Security
|
||||
|
||||
- **Non-root execution**: The application runs as a non-privileged user
|
||||
- **Minimal attack surface**: Built from scratch base image
|
||||
- **No shell access**: Only the application binary is available
|
||||
- **Read-only filesystem**: Container filesystem is read-only except for mounted volumes
|
||||
|
||||
## Development
|
||||
|
||||
### Code Quality
|
||||
@ -329,6 +455,8 @@ This is a utility tool for educational content conversion. Please ensure you hav
|
||||
[Build]: https://github.com/kjanat/articulate-parser/actions/workflows/ci.yml
|
||||
[Codecov]: https://codecov.io/gh/kjanat/articulate-parser
|
||||
[Commits]: https://github.com/kjanat/articulate-parser/commits/master/
|
||||
[Docker workflow]: https://github.com/kjanat/articulate-parser/actions/workflows/docker.yml
|
||||
[Docker image]: https://github.com/kjanat/articulate-parser/pkgs/container/articulate-parser
|
||||
[Go report]: https://goreportcard.com/report/github.com/kjanat/articulate-parser
|
||||
[gomod]: go.mod
|
||||
[Issues]: https://github.com/kjanat/articulate-parser/issues
|
||||
|
||||
39
docker-compose.yml
Normal file
39
docker-compose.yml
Normal file
@ -0,0 +1,39 @@
|
||||
services:
|
||||
articulate-parser: &articulate-parser
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
VERSION: "dev"
|
||||
BUILD_TIME: "2024-01-01T00:00:00Z"
|
||||
GIT_COMMIT: "dev"
|
||||
image: articulate-parser:local
|
||||
volumes:
|
||||
# Mount current directory to /workspace for file access
|
||||
- .:/workspace
|
||||
working_dir: /workspace
|
||||
# Override entrypoint for interactive use
|
||||
entrypoint: ["/articulate-parser"]
|
||||
# Default to showing help
|
||||
command: ["--help"]
|
||||
|
||||
# Service for processing files with volume mounts
|
||||
parser-with-files:
|
||||
<<: *articulate-parser
|
||||
volumes:
|
||||
- ./input:/input:ro
|
||||
- ./output:/output
|
||||
command: ["/input/sample.json", "markdown", "/output/result.md"]
|
||||
|
||||
# Service for development - with shell access
|
||||
parser-dev:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
image: articulate-parser:dev
|
||||
volumes:
|
||||
- .:/workspace
|
||||
working_dir: /workspace
|
||||
entrypoint: ["/bin/sh"]
|
||||
command: ["-c", "while true; do sleep 30; done"]
|
||||
# Uses Dockerfile.dev with Alpine base instead of scratch for shell access
|
||||
@ -5,7 +5,7 @@ package version
|
||||
// Version information.
|
||||
var (
|
||||
// Version is the current version of the application.
|
||||
Version = "0.3.1"
|
||||
Version = "0.4.1"
|
||||
|
||||
// BuildTime is the time the binary was built.
|
||||
BuildTime = "unknown"
|
||||
|
||||
54
main.go
54
main.go
@ -7,9 +7,11 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/kjanat/articulate-parser/internal/exporters"
|
||||
"github.com/kjanat/articulate-parser/internal/services"
|
||||
"github.com/kjanat/articulate-parser/internal/version"
|
||||
)
|
||||
|
||||
// main is the entry point of the application.
|
||||
@ -28,15 +30,23 @@ func run(args []string) int {
|
||||
exporterFactory := exporters.NewFactory(htmlCleaner)
|
||||
app := services.NewApp(parser, exporterFactory)
|
||||
|
||||
// Check for version flag
|
||||
if len(args) > 1 && (args[1] == "--version" || args[1] == "-v") {
|
||||
fmt.Printf("%s version %s\n", args[0], version.Version)
|
||||
fmt.Printf("Build time: %s\n", version.BuildTime)
|
||||
fmt.Printf("Git commit: %s\n", version.GitCommit)
|
||||
return 0
|
||||
}
|
||||
|
||||
// Check for help flag
|
||||
if len(args) > 1 && (args[1] == "--help" || args[1] == "-h" || args[1] == "help") {
|
||||
printUsage(args[0], app.GetSupportedFormats())
|
||||
return 0
|
||||
}
|
||||
|
||||
// Check for required command-line arguments
|
||||
if len(args) < 4 {
|
||||
fmt.Printf("Usage: %s <source> <format> <output>\n", args[0])
|
||||
fmt.Printf(" source: URI or file path to the course\n")
|
||||
fmt.Printf(" format: export format (%s)\n", joinStrings(app.GetSupportedFormats(), ", "))
|
||||
fmt.Printf(" output: output file path\n")
|
||||
fmt.Println("\nExample:")
|
||||
fmt.Printf(" %s articulate-sample.json markdown output.md\n", args[0])
|
||||
fmt.Printf(" %s https://rise.articulate.com/share/xyz docx output.docx\n", args[0])
|
||||
printUsage(args[0], app.GetSupportedFormats())
|
||||
return 1
|
||||
}
|
||||
|
||||
@ -73,25 +83,17 @@ func isURI(str string) bool {
|
||||
return len(str) > 7 && (str[:7] == "http://" || str[:8] == "https://")
|
||||
}
|
||||
|
||||
// joinStrings concatenates a slice of strings using the specified separator.
|
||||
// printUsage prints the command-line usage information.
|
||||
//
|
||||
// Parameters:
|
||||
// - strs: The slice of strings to join
|
||||
// - sep: The separator to insert between each string
|
||||
//
|
||||
// Returns:
|
||||
// - A single string with all elements joined by the separator
|
||||
func joinStrings(strs []string, sep string) string {
|
||||
if len(strs) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(strs) == 1 {
|
||||
return strs[0]
|
||||
}
|
||||
|
||||
result := strs[0]
|
||||
for i := 1; i < len(strs); i++ {
|
||||
result += sep + strs[i]
|
||||
}
|
||||
return result
|
||||
// - programName: The name of the program (args[0])
|
||||
// - supportedFormats: Slice of supported export formats
|
||||
func printUsage(programName string, supportedFormats []string) {
|
||||
fmt.Printf("Usage: %s <source> <format> <output>\n", programName)
|
||||
fmt.Printf(" source: URI or file path to the course\n")
|
||||
fmt.Printf(" format: export format (%s)\n", strings.Join(supportedFormats, ", "))
|
||||
fmt.Printf(" output: output file path\n")
|
||||
fmt.Println("\nExample:")
|
||||
fmt.Printf(" %s articulate-sample.json markdown output.md\n", programName)
|
||||
fmt.Printf(" %s https://rise.articulate.com/share/xyz docx output.docx\n", programName)
|
||||
}
|
||||
|
||||
179
main_test.go
179
main_test.go
@ -85,80 +85,6 @@ func TestIsURI(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestJoinStrings tests the joinStrings function with various input scenarios.
|
||||
func TestJoinStrings(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
strs []string
|
||||
separator string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "empty slice",
|
||||
strs: []string{},
|
||||
separator: ", ",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "single string",
|
||||
strs: []string{"hello"},
|
||||
separator: ", ",
|
||||
expected: "hello",
|
||||
},
|
||||
{
|
||||
name: "two strings with comma separator",
|
||||
strs: []string{"markdown", "docx"},
|
||||
separator: ", ",
|
||||
expected: "markdown, docx",
|
||||
},
|
||||
{
|
||||
name: "three strings with comma separator",
|
||||
strs: []string{"markdown", "md", "docx"},
|
||||
separator: ", ",
|
||||
expected: "markdown, md, docx",
|
||||
},
|
||||
{
|
||||
name: "multiple strings with pipe separator",
|
||||
strs: []string{"option1", "option2", "option3"},
|
||||
separator: " | ",
|
||||
expected: "option1 | option2 | option3",
|
||||
},
|
||||
{
|
||||
name: "strings with no separator",
|
||||
strs: []string{"a", "b", "c"},
|
||||
separator: "",
|
||||
expected: "abc",
|
||||
},
|
||||
{
|
||||
name: "strings with newline separator",
|
||||
strs: []string{"line1", "line2", "line3"},
|
||||
separator: "\n",
|
||||
expected: "line1\nline2\nline3",
|
||||
},
|
||||
{
|
||||
name: "empty strings in slice",
|
||||
strs: []string{"", "middle", ""},
|
||||
separator: "-",
|
||||
expected: "-middle-",
|
||||
},
|
||||
{
|
||||
name: "nil slice",
|
||||
strs: nil,
|
||||
separator: ", ",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := joinStrings(tt.strs, tt.separator)
|
||||
if result != tt.expected {
|
||||
t.Errorf("joinStrings(%v, %q) = %q, want %q", tt.strs, tt.separator, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkIsURI benchmarks the isURI function performance.
|
||||
func BenchmarkIsURI(b *testing.B) {
|
||||
testStr := "https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/"
|
||||
@ -169,17 +95,6 @@ func BenchmarkIsURI(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkJoinStrings benchmarks the joinStrings function performance.
|
||||
func BenchmarkJoinStrings(b *testing.B) {
|
||||
strs := []string{"markdown", "md", "docx", "word", "pdf", "html"}
|
||||
separator := ", "
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
joinStrings(strs, separator)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWithInsufficientArgs tests the run function with insufficient command-line arguments.
|
||||
func TestRunWithInsufficientArgs(t *testing.T) {
|
||||
tests := []struct {
|
||||
@ -236,6 +151,100 @@ func TestRunWithInsufficientArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWithHelpFlags tests the run function with help flag arguments.
|
||||
func TestRunWithHelpFlags(t *testing.T) {
|
||||
helpFlags := []string{"--help", "-h", "help"}
|
||||
|
||||
for _, flag := range helpFlags {
|
||||
t.Run("help_flag_"+flag, func(t *testing.T) {
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Run with help flag
|
||||
args := []string{"articulate-parser", flag}
|
||||
exitCode := run(args)
|
||||
|
||||
// Restore stdout
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
|
||||
// Read captured output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify exit code is 0 (success)
|
||||
if exitCode != 0 {
|
||||
t.Errorf("Expected exit code 0 for help flag %s, got %d", flag, exitCode)
|
||||
}
|
||||
|
||||
// Verify help content is displayed
|
||||
expectedContent := []string{
|
||||
"Usage:",
|
||||
"source: URI or file path to the course",
|
||||
"format: export format",
|
||||
"output: output file path",
|
||||
"Example:",
|
||||
"articulate-sample.json markdown output.md",
|
||||
"https://rise.articulate.com/share/xyz docx output.docx",
|
||||
}
|
||||
|
||||
for _, expected := range expectedContent {
|
||||
if !strings.Contains(output, expected) {
|
||||
t.Errorf("Expected help output to contain %q when using flag %s, got: %s", expected, flag, output)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWithVersionFlags tests the run function with version flag arguments.
|
||||
func TestRunWithVersionFlags(t *testing.T) {
|
||||
versionFlags := []string{"--version", "-v"}
|
||||
|
||||
for _, flag := range versionFlags {
|
||||
t.Run("version_flag_"+flag, func(t *testing.T) {
|
||||
// Capture stdout
|
||||
oldStdout := os.Stdout
|
||||
r, w, _ := os.Pipe()
|
||||
os.Stdout = w
|
||||
|
||||
// Run with version flag
|
||||
args := []string{"articulate-parser", flag}
|
||||
exitCode := run(args)
|
||||
|
||||
// Restore stdout
|
||||
w.Close()
|
||||
os.Stdout = oldStdout
|
||||
|
||||
// Read captured output
|
||||
var buf bytes.Buffer
|
||||
io.Copy(&buf, r)
|
||||
output := buf.String()
|
||||
|
||||
// Verify exit code is 0 (success)
|
||||
if exitCode != 0 {
|
||||
t.Errorf("Expected exit code 0 for version flag %s, got %d", flag, exitCode)
|
||||
}
|
||||
|
||||
// Verify version content is displayed
|
||||
expectedContent := []string{
|
||||
"articulate-parser version",
|
||||
"Build time:",
|
||||
"Git commit:",
|
||||
}
|
||||
|
||||
for _, expected := range expectedContent {
|
||||
if !strings.Contains(output, expected) {
|
||||
t.Errorf("Expected version output to contain %q when using flag %s, got: %s", expected, flag, output)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunWithInvalidFile tests the run function with a non-existent file.
|
||||
func TestRunWithInvalidFile(t *testing.T) {
|
||||
// Capture stdout and stderr
|
||||
|
||||
Reference in New Issue
Block a user