3 Commits

Author SHA1 Message Date
a1a49a75b7 chore: Enhance developer tooling and documentation
Adds `actionlint` to the pre-commit configuration to validate GitHub Actions workflows.

Significantly expands the `AGENTS.md` file with a comprehensive summary of new features and changes in Go 1.24 and 1.25, along with actionable recommendations for the project.

Additionally, normalizes markdown list formatting across various documentation files for consistency.
2025-11-07 07:50:09 +01:00
8d606706e2 chore(tooling): Improve CI pipeline and expand pre-commit hooks
Expands the pre-commit configuration with a wider range of hooks to enforce file quality, validation, security, and Git safety checks.

The CI pipeline is updated to:
- Correct the `golangci-lint` format command to `fmt`.
- Enable CGO for test execution to support the race detector.
- Improve the robustness of test report parsing scripts.

Additionally, this commit includes minor stylistic and formatting cleanups across various project files.
2025-11-07 07:30:11 +01:00
e7de5d044a chore(ci): remove Go versions below 1.24 from CI matrix
Remove CI test runs for Go 1.21.x, 1.22.x, and 1.23.x as the minimum
supported version is 1.24.0 (as defined in go.mod).

This change:
- Removes outdated Go versions from the test matrix
- Aligns CI testing with the minimum supported version
- Reduces CI execution time by removing unnecessary test runs
- Maintains testing coverage for supported versions (1.24.x, 1.25.x)

fix(ci): remove impossible dependencies from docker job

The docker job runs on push events to master/develop branches, but it
was depending on docker-test and dependency-review jobs which only run
on pull_request events. This created an impossible dependency chain
that prevented the docker job from ever running.

This change:
- Removes docker-test and dependency-review from docker job dependencies
- Keeps only the test job as a dependency (which runs on both events)
- Allows docker build & push to run correctly on push events
- Maintains PR-specific checks (docker-test, dependency-review) for PRs

chore(tooling): add pre-commit configuration

Introduces a `.pre-commit-config.yaml` file to automate code quality checks before commits.

This configuration includes standard hooks for file hygiene (e.g., trailing whitespace, end-of-file fixes) and integrates `golangci-lint` to lint and format Go code on staged files. This helps enforce code style and catch issues early in the development process.
2025-11-07 07:06:10 +01:00
26 changed files with 1011 additions and 686 deletions

View File

@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our Examples of behavior that contributes to a positive environment for our
community include: community include:
- Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback - Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, - Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
- Focusing on what is best not just for us as individuals, but for the - Focusing on what is best not just for us as individuals, but for the
overall community overall community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or - The use of sexualized language or imagery, and sexual attention or
advances of any kind advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks - Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment - Public or private harassment
- Publishing others' private information, such as a physical or email - Publishing others' private information, such as a physical or email
address, without their explicit permission address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a - Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
## Enforcement Responsibilities ## Enforcement Responsibilities

View File

@ -12,38 +12,38 @@ This project and everyone participating in it is governed by our Code of Conduct
Before creating bug reports, please check existing issues as you might find that the issue has already been reported. When creating a bug report, include as many details as possible: Before creating bug reports, please check existing issues as you might find that the issue has already been reported. When creating a bug report, include as many details as possible:
- Use the bug report template - Use the bug report template
- Include sample Articulate Rise content that reproduces the issue - Include sample Articulate Rise content that reproduces the issue
- Provide your environment details (OS, Go version, etc.) - Provide your environment details (OS, Go version, etc.)
- Include error messages and stack traces - Include error messages and stack traces
### Suggesting Enhancements ### Suggesting Enhancements
Enhancement suggestions are welcome! Please use the feature request template and include: Enhancement suggestions are welcome! Please use the feature request template and include:
- A clear description of the enhancement - A clear description of the enhancement
- Your use case and why this would be valuable - Your use case and why this would be valuable
- Any implementation ideas you might have - Any implementation ideas you might have
### Pull Requests ### Pull Requests
1. **Fork the repository** and create your branch from `master` 1. **Fork the repository** and create your branch from `master`
2. **Make your changes** following our coding standards 2. **Make your changes** following our coding standards
3. **Add tests** for any new functionality 3. **Add tests** for any new functionality
4. **Ensure all tests pass** by running `go test ./...` 4. **Ensure all tests pass** by running `go test ./...`
5. **Run `go fmt`** to format your code 5. **Run `go fmt`** to format your code
6. **Run `go vet`** to check for common issues 6. **Run `go vet`** to check for common issues
7. **Update documentation** if needed 7. **Update documentation** if needed
8. **Create a pull request** with a clear title and description 8. **Create a pull request** with a clear title and description
## Development Setup ## Development Setup
1. **Prerequisites:** 1. **Prerequisites:**
- Go 1.21 or later - Go 1.21 or later
- Git - Git
2. **Clone and setup:** 2. **Clone and setup:**
```bash ```bash
git clone https://github.com/your-username/articulate-parser.git git clone https://github.com/your-username/articulate-parser.git
@ -51,13 +51,13 @@ Enhancement suggestions are welcome! Please use the feature request template and
go mod download go mod download
``` ```
3. **Run tests:** 3. **Run tests:**
```bash ```bash
go test -v ./... go test -v ./...
``` ```
4. **Build:** 4. **Build:**
```bash ```bash
go build main.go go build main.go
@ -67,18 +67,18 @@ Enhancement suggestions are welcome! Please use the feature request template and
### Go Style Guide ### Go Style Guide
- Follow the [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) - Follow the [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
- Use `gofmt` to format your code - Use `gofmt` to format your code
- Use meaningful variable and function names - Use meaningful variable and function names
- Add comments for exported functions and types - Add comments for exported functions and types
- Keep functions focused and small - Keep functions focused and small
### Testing ### Testing
- Write tests for new functionality - Write tests for new functionality
- Use table-driven tests where appropriate - Use table-driven tests where appropriate
- Aim for good test coverage - Aim for good test coverage
- Test error cases and edge conditions - Test error cases and edge conditions
### Commit Messages ### Commit Messages
@ -112,19 +112,19 @@ articulate-parser/
### New Content Types ### New Content Types
1. Add the content type definition to `types/` 1. Add the content type definition to `types/`
2. Implement parsing logic in `parser/` 2. Implement parsing logic in `parser/`
3. Add export handling in `exporters/` 3. Add export handling in `exporters/`
4. Write comprehensive tests 4. Write comprehensive tests
5. Update documentation 5. Update documentation
### New Export Formats ### New Export Formats
1. Create a new exporter in `exporters/` 1. Create a new exporter in `exporters/`
2. Implement the `Exporter` interface 2. Implement the `Exporter` interface
3. Add CLI support in `main.go` 3. Add CLI support in `main.go`
4. Add tests with sample output 4. Add tests with sample output
5. Update README with usage examples 5. Update README with usage examples
## Testing ## Testing
@ -146,31 +146,31 @@ go test -run TestSpecificFunction ./...
### Test Data ### Test Data
- Add sample Articulate Rise JSON files to `tests/data/` - Add sample Articulate Rise JSON files to `tests/data/`
- Include both simple and complex content examples - Include both simple and complex content examples
- Test edge cases and error conditions - Test edge cases and error conditions
## Documentation ## Documentation
- Update the README for user-facing changes - Update the README for user-facing changes
- Add inline code comments for complex logic - Add inline code comments for complex logic
- Update examples when adding new features - Update examples when adding new features
- Keep the feature list current - Keep the feature list current
## Release Process ## Release Process
Releases are handled by maintainers: Releases are handled by maintainers:
1. Version bumping follows semantic versioning 1. Version bumping follows semantic versioning
2. Releases are created from the `master` branch 2. Releases are created from the `master` branch
3. GitHub Actions automatically builds and publishes releases 3. GitHub Actions automatically builds and publishes releases
4. Release notes are auto-generated from commits 4. Release notes are auto-generated from commits
## Questions? ## Questions?
- Open a discussion for general questions - Open a discussion for general questions
- Use the question issue template for specific help - Use the question issue template for specific help
- Check existing issues and documentation first - Check existing issues and documentation first
## Recognition ## Recognition

View File

@ -10,26 +10,26 @@ Fixes #
## Type of Change ## Type of Change
<!-- Mark the appropriate option with an "x" --> <!-- Mark the appropriate option with an "x" -->
- [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality) - [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update - [ ] Documentation update
- [ ] Performance improvement - [ ] Performance improvement
- [ ] Code refactoring (no functional changes) - [ ] Code refactoring (no functional changes)
- [ ] Test updates - [ ] Test updates
## Checklist ## Checklist
<!-- Mark the items you've completed with an "x" --> <!-- Mark the items you've completed with an "x" -->
- [ ] My code follows the style guidelines of this project - [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code - [ ] I have performed a self-review of my code
- [ ] I have added comments to complex logic - [ ] I have added comments to complex logic
- [ ] I have updated the documentation - [ ] I have updated the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes - [ ] New and existing unit tests pass locally with my changes
- [ ] I have checked for potential breaking changes - [ ] I have checked for potential breaking changes
- [ ] No new warnings are generated - [ ] No new warnings are generated
- [ ] The commit message follows our guidelines - [ ] The commit message follows our guidelines
## Screenshots (if appropriate) ## Screenshots (if appropriate)

32
.github/SECURITY.md vendored
View File

@ -13,32 +13,32 @@ Currently, the following versions of Articulate Rise Parser are supported with s
We take the security of Articulate Rise Parser seriously. If you believe you have found a security vulnerability, please follow these steps: We take the security of Articulate Rise Parser seriously. If you believe you have found a security vulnerability, please follow these steps:
1. **Do not disclose the vulnerability publicly** - Please do not create a public GitHub issue for security vulnerabilities. 1. **Do not disclose the vulnerability publicly** - Please do not create a public GitHub issue for security vulnerabilities.
2. **Email the details to [security+articulate-parser@kjanat.com]** - Include as much information as possible about the vulnerability. 2. **Email the details to [security+articulate-parser@kjanat.com]** - Include as much information as possible about the vulnerability.
3. **Wait for a response** - We will acknowledge your email within 48 hours and provide an estimated timeline for a fix. 3. **Wait for a response** - We will acknowledge your email within 48 hours and provide an estimated timeline for a fix.
4. **Work with us** - We may ask for additional information to help us understand and address the issue. 4. **Work with us** - We may ask for additional information to help us understand and address the issue.
## What to Include in a Report ## What to Include in a Report
When reporting a vulnerability, please include: When reporting a vulnerability, please include:
- A clear description of the issue - A clear description of the issue
- Steps to reproduce the vulnerability - Steps to reproduce the vulnerability
- The potential impact of the vulnerability - The potential impact of the vulnerability
- Any possible mitigations you've identified - Any possible mitigations you've identified
## What to Expect ## What to Expect
- We will acknowledge receipt of your vulnerability report within 48 hours. - We will acknowledge receipt of your vulnerability report within 48 hours.
- We will provide regular updates about our progress. - We will provide regular updates about our progress.
- We will notify you when the vulnerability is fixed. - We will notify you when the vulnerability is fixed.
- With your permission, we will include your name in the acknowledgments. - With your permission, we will include your name in the acknowledgments.
## Security Measures ## Security Measures
This project follows these security practices: This project follows these security practices:
- Regular dependency updates via Dependabot - Regular dependency updates via Dependabot
- CodeQL security scanning - CodeQL security scanning
- Automated testing for each pull request - Automated testing for each pull request
- Code review requirements for all changes - Code review requirements for all changes

View File

@ -2,7 +2,7 @@ name: autofix.ci
on: on:
pull_request: pull_request:
push: push:
branches: ["master"] branches: [ "master" ]
permissions: permissions:
contents: read contents: read
@ -11,18 +11,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v5
- name: Install Task - name: Install Task
uses: go-task/setup-task@v1 uses: go-task/setup-task@v1
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: { go-version-file: "go.mod" } with: { go-version-file: 'go.mod' }
- name: Setup go deps - name: Setup go deps
run: | run: |
# Install golangci-lint # Install golangci-lint
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b "$(go env GOPATH)/bin"
# Install go-task dependencies # Install go-task dependencies
go install golang.org/x/tools/cmd/goimports@latest go install golang.org/x/tools/cmd/goimports@latest

View File

@ -2,7 +2,7 @@ name: CI
on: on:
push: push:
branches: ["master", "develop"] branches: ['master', 'develop']
pull_request: pull_request:
env: env:
@ -21,12 +21,12 @@ jobs:
contents: read contents: read
pull-requests: read pull-requests: read
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- uses: actions/setup-go@v6 - uses: actions/setup-go@v6
with: with:
go-version: stable go-version: stable
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v9 uses: golangci/golangci-lint-action@v8
with: { version: latest } with: { version: latest }
test: test:
@ -42,7 +42,7 @@ jobs:
- 1.25.x - 1.25.x
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v5
- name: Set up Go ${{ matrix.go }} - name: Set up Go ${{ matrix.go }}
uses: actions/setup-go@v6 uses: actions/setup-go@v6
@ -64,14 +64,18 @@ jobs:
- name: Run tests with enhanced reporting - name: Run tests with enhanced reporting
id: test id: test
env:
CGO_ENABLED: 1
run: | run: |
cat >> $GITHUB_STEP_SUMMARY << EOF {
cat << EOF
## 🔧 Test Environment ## 🔧 Test Environment
- **Go Version:** ${{ matrix.go }} - **Go Version:** ${{ matrix.go }}
- **OS:** ubuntu-latest - **OS:** ubuntu-latest
- **Timestamp:** $(date -u) - **Timestamp:** $(date -u)
EOF EOF
} >> "$GITHUB_STEP_SUMMARY"
echo "Running tests with coverage..." echo "Running tests with coverage..."
task test:coverage 2>&1 | tee test-output.log task test:coverage 2>&1 | tee test-output.log
@ -79,21 +83,22 @@ jobs:
# Extract test results for summary # Extract test results for summary
TEST_STATUS=$? TEST_STATUS=$?
TOTAL_TESTS=$(grep -c "=== RUN" test-output.log || echo "0") TOTAL_TESTS=$(grep -c "=== RUN" test-output.log || echo "0")
PASSED_TESTS=$(grep -c "--- PASS:" test-output.log || echo "0") PASSED_TESTS=$(grep -c -- "--- PASS:" test-output.log || echo "0")
FAILED_TESTS=$(grep -c "--- FAIL:" test-output.log || echo "0") FAILED_TESTS=$(grep -c -- "--- FAIL:" test-output.log || echo "0")
SKIPPED_TESTS=$(grep -c "--- SKIP:" test-output.log || echo "0") SKIPPED_TESTS=$(grep -c -- "--- SKIP:" test-output.log || echo "0")
# Generate test summary # Generate test summary
cat >> $GITHUB_STEP_SUMMARY << EOF {
cat << EOF
## 🧪 Test Results (Go ${{ matrix.go }}) ## 🧪 Test Results (Go ${{ matrix.go }})
| Metric | Value | | Metric | Value |
| ----------- | ----------------------------------------------------------- | | ----------- | ------------------------------------------------------------- |
| Total Tests | $TOTAL_TESTS | | Total Tests | $TOTAL_TESTS |
| Passed | $PASSED_TESTS | | Passed | $PASSED_TESTS |
| Failed | $FAILED_TESTS | | Failed | $FAILED_TESTS |
| Skipped | $SKIPPED_TESTS | | Skipped | $SKIPPED_TESTS |
| Status | $([ $TEST_STATUS -eq 0 ] && echo "PASSED" || echo "FAILED") | | Status | $([ "$TEST_STATUS" -eq 0 ] && echo "PASSED" || echo "FAILED") |
### 📦 Package Test Results ### 📦 Package Test Results
@ -105,38 +110,39 @@ jobs:
grep "^ok\|^FAIL" test-output.log | while read -r line; do grep "^ok\|^FAIL" test-output.log | while read -r line; do
if [[ $line == ok* ]]; then if [[ $line == ok* ]]; then
pkg=$(echo "$line" | awk '{print $2}') pkg=$(echo "$line" | awk '{print $2}')
echo "| $pkg | ✅ PASS |" >> $GITHUB_STEP_SUMMARY echo "| $pkg | ✅ PASS |"
elif [[ $line == FAIL* ]]; then elif [[ $line == FAIL* ]]; then
pkg=$(echo "$line" | awk '{print $2}') pkg=$(echo "$line" | awk '{print $2}')
echo "| $pkg | ❌ FAIL |" >> $GITHUB_STEP_SUMMARY echo "| $pkg | ❌ FAIL |"
fi fi
done done
echo "" >> $GITHUB_STEP_SUMMARY echo ""
# Add detailed results if tests failed # Add detailed results if tests failed
if [ $TEST_STATUS -ne 0 ]; then if [ "$TEST_STATUS" -ne 0 ]; then
cat >> $GITHUB_STEP_SUMMARY << 'EOF' cat << 'EOF'
### ❌ Failed Tests Details ### ❌ Failed Tests Details
``` ```
EOF EOF
grep -A 10 "--- FAIL:" test-output.log | head -100 >> $GITHUB_STEP_SUMMARY grep -A 10 -- "--- FAIL:" test-output.log | head -100
cat >> $GITHUB_STEP_SUMMARY << 'EOF' cat << 'EOF'
``` ```
EOF EOF
fi fi
} >> "$GITHUB_STEP_SUMMARY"
# Set outputs for other steps # Set outputs for other steps
cat >> $GITHUB_OUTPUT << EOF {
test-status=$TEST_STATUS echo "test-status=$TEST_STATUS"
total-tests=$TOTAL_TESTS echo "total-tests=$TOTAL_TESTS"
passed-tests=$PASSED_TESTS echo "passed-tests=$PASSED_TESTS"
failed-tests=$FAILED_TESTS echo "failed-tests=$FAILED_TESTS"
EOF } >> "$GITHUB_OUTPUT"
# Exit with the original test status # Exit with the original test status
exit $TEST_STATUS exit "$TEST_STATUS"
- name: Generate coverage report - name: Generate coverage report
if: always() if: always()
@ -144,7 +150,8 @@ jobs:
if [ -f coverage/coverage.out ]; then if [ -f coverage/coverage.out ]; then
COVERAGE=$(go tool cover -func=coverage/coverage.out | grep total | awk '{print $3}') COVERAGE=$(go tool cover -func=coverage/coverage.out | grep total | awk '{print $3}')
cat >> $GITHUB_STEP_SUMMARY << EOF {
cat << EOF
## 📊 Code Coverage (Go ${{ matrix.go }}) ## 📊 Code Coverage (Go ${{ matrix.go }})
**Total Coverage: $COVERAGE** **Total Coverage: $COVERAGE**
@ -156,45 +163,46 @@ jobs:
| ------- | -------- | | ------- | -------- |
EOF EOF
# Create temporary file for package coverage aggregation # Create temporary file for package coverage aggregation
temp_coverage=$(mktemp) temp_coverage=$(mktemp)
# Extract package-level coverage data # Extract package-level coverage data
go tool cover -func=coverage/coverage.out | grep -v total | while read -r line; do go tool cover -func=coverage/coverage.out | grep -v total | while read -r 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}')
pkg_path=$(dirname "$filepath" | sed 's|github.com/kjanat/articulate-parser/||; s|^\./||') pkg_path=$(dirname "$filepath" | sed 's|github.com/kjanat/articulate-parser/||; s|^\./||')
coverage=$(echo "$line" | awk '{print $3}' | sed 's/%//') coverage=$(echo "$line" | awk '{print $3}' | sed 's/%//')
# Use root package if no subdirectory # Use root package if no subdirectory
[[ "$pkg_path" == "." || "$pkg_path" == "" ]] && pkg_path="root" [[ "$pkg_path" == "." || "$pkg_path" == "" ]] && pkg_path="root"
echo "$pkg_path $coverage" >> "$temp_coverage" echo "$pkg_path $coverage" >> "$temp_coverage"
fi fi
done done
# Aggregate coverage by package (average) # Aggregate coverage by package (average)
awk '{ awk '{
packages[$1] += $2 packages[$1] += $2
counts[$1]++ counts[$1]++
}
END {
for (pkg in packages) {
avg = packages[pkg] / counts[pkg]
printf "| %s | %.1f%% |\n", pkg, avg
} }
}' "$temp_coverage" | sort >> $GITHUB_STEP_SUMMARY END {
for (pkg in packages) {
avg = packages[pkg] / counts[pkg]
printf "| %s | %.1f%% |\n", pkg, avg
}
}' "$temp_coverage" | sort
rm -f "$temp_coverage" rm -f "$temp_coverage"
cat >> $GITHUB_STEP_SUMMARY << 'EOF' cat << 'EOF'
</details> </details>
EOF EOF
} >> "$GITHUB_STEP_SUMMARY"
else else
cat >> $GITHUB_STEP_SUMMARY << 'EOF' cat >> "$GITHUB_STEP_SUMMARY" << 'EOF'
## ⚠️ Coverage Report ## ⚠️ Coverage Report
No coverage file generated No coverage file generated
@ -203,7 +211,7 @@ jobs:
- name: Upload test artifacts - name: Upload test artifacts
if: failure() if: failure()
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v5
with: with:
name: test-results-go-${{ matrix.go }} name: test-results-go-${{ matrix.go }}
path: | path: |
@ -214,51 +222,53 @@ jobs:
- name: Run linters - name: Run linters
run: | run: |
# Initialize summary # Initialize summary
cat >> $GITHUB_STEP_SUMMARY << EOF {
cat << EOF
## 🔍 Static Analysis (Go ${{ matrix.go }}) ## 🔍 Static Analysis (Go ${{ matrix.go }})
EOF EOF
# Run go vet # Run go vet
VET_OUTPUT=$(task lint:vet 2>&1 || echo "") VET_OUTPUT=$(task lint:vet 2>&1 || echo "")
VET_STATUS=$? VET_STATUS=$?
if [ $VET_STATUS -eq 0 ]; then if [ "$VET_STATUS" -eq 0 ]; then
echo "✅ **go vet:** No issues found" >> $GITHUB_STEP_SUMMARY echo "✅ **go vet:** No issues found"
else else
cat >> $GITHUB_STEP_SUMMARY << 'EOF' cat << 'EOF'
❌ **go vet:** Issues found ❌ **go vet:** Issues found
``` ```
EOF EOF
echo "$VET_OUTPUT" >> $GITHUB_STEP_SUMMARY echo "$VET_OUTPUT"
echo '```' >> $GITHUB_STEP_SUMMARY echo '```'
fi fi
echo "" >> $GITHUB_STEP_SUMMARY echo ""
# Run go fmt check # Run go fmt check
FMT_OUTPUT=$(task lint:fmt 2>&1 || echo "") FMT_OUTPUT=$(task lint:fmt 2>&1 || echo "")
FMT_STATUS=$? FMT_STATUS=$?
if [ $FMT_STATUS -eq 0 ]; then if [ "$FMT_STATUS" -eq 0 ]; then
echo "✅ **go fmt:** All files properly formatted" >> $GITHUB_STEP_SUMMARY echo "✅ **go fmt:** All files properly formatted"
else else
cat >> $GITHUB_STEP_SUMMARY << 'EOF' cat << 'EOF'
❌ **go fmt:** Files need formatting ❌ **go fmt:** Files need formatting
``` ```
EOF EOF
echo "$FMT_OUTPUT" >> $GITHUB_STEP_SUMMARY echo "$FMT_OUTPUT"
echo '```' >> $GITHUB_STEP_SUMMARY echo '```'
fi fi
} >> "$GITHUB_STEP_SUMMARY"
# Exit with error if any linter failed # Exit with error if any linter failed
[ $VET_STATUS -eq 0 ] && [ $FMT_STATUS -eq 0 ] || exit 1 [ "$VET_STATUS" -eq 0 ] && [ "$FMT_STATUS" -eq 0 ] || exit 1
- name: Job Summary - name: Job Summary
if: always() if: always()
run: | run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF' cat >> "$GITHUB_STEP_SUMMARY" << 'EOF'
## 📋 Job Summary (Go ${{ matrix.go }}) ## 📋 Job Summary (Go ${{ matrix.go }})
| Step | Status | | Step | Status |
@ -294,7 +304,7 @@ jobs:
contents: read contents: read
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v5
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
@ -313,24 +323,26 @@ jobs:
- name: Test Docker image using Task - name: Test Docker image using Task
run: | run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF' {
cat << 'EOF'
## 🧪 Docker Image Tests ## 🧪 Docker Image Tests
EOF EOF
# Run Task docker test # Run Task docker test
task docker:test task docker:test
echo "**Testing help command:**" >> $GITHUB_STEP_SUMMARY echo "**Testing help command:**"
echo '```terminaloutput' >> $GITHUB_STEP_SUMMARY echo '```terminaloutput'
docker run --rm articulate-parser:latest --help >> $GITHUB_STEP_SUMMARY docker run --rm articulate-parser:latest --help
echo '```' >> $GITHUB_STEP_SUMMARY echo '```'
echo "" >> $GITHUB_STEP_SUMMARY echo ""
# Test image size # Test image size
IMAGE_SIZE=$(docker image inspect articulate-parser: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"
echo "" >> $GITHUB_STEP_SUMMARY echo ""
} >> "$GITHUB_STEP_SUMMARY"
dependency-review: dependency-review:
name: Dependency Review name: Dependency Review
@ -339,10 +351,10 @@ jobs:
contents: read contents: read
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
- name: "Checkout Repository" - name: 'Checkout Repository'
uses: actions/checkout@v6 uses: actions/checkout@v5
- name: "Dependency Review" - name: 'Dependency Review'
uses: actions/dependency-review-action@v4 uses: actions/dependency-review-action@v4
with: with:
fail-on-severity: moderate fail-on-severity: moderate
@ -354,14 +366,14 @@ jobs:
permissions: permissions:
contents: read contents: read
packages: write packages: write
needs: [test, docker-test, dependency-review] needs: [test]
if: | if: |
github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.event_name == 'push' && (github.ref == 'refs/heads/master' ||
github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/develop' ||
startsWith(github.ref, 'refs/heads/feature/docker')) startsWith(github.ref, 'refs/heads/feature/docker'))
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v5
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3
@ -433,7 +445,7 @@ jobs:
- name: Generate Docker summary - name: Generate Docker summary
run: | run: |
cat >> $GITHUB_STEP_SUMMARY << 'EOF' cat >> "$GITHUB_STEP_SUMMARY" << 'EOF'
## 🐳 Docker Build Summary ## 🐳 Docker Build Summary
**Image:** `ghcr.io/${{ github.repository }}` **Image:** `ghcr.io/${{ github.repository }}`

View File

@ -61,7 +61,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v5
# Add any setup steps before running the `github/codeql-action/init` action. # Add any setup steps before running the `github/codeql-action/init` action.
# This includes steps like installing compilers or runtimes (`actions/setup-node` # This includes steps like installing compilers or runtimes (`actions/setup-node`

View File

@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout Repository' - name: 'Checkout Repository'
uses: actions/checkout@v6 uses: actions/checkout@v5
- name: 'Dependency Review' - name: 'Dependency Review'
uses: actions/dependency-review-action@v4 uses: actions/dependency-review-action@v4

View File

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v6 uses: actions/checkout@v5
with: with:
fetch-depth: 0 fetch-depth: 0
@ -88,7 +88,7 @@ jobs:
packages: write packages: write
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v5
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v3

75
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,75 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
# File quality
- id: trailing-whitespace
exclude: '^\.github/ISSUE_TEMPLATE/.*\.yml$'
- id: end-of-file-fixer
- id: mixed-line-ending
args: ['--fix=lf']
# File validation
- id: check-yaml
- id: check-json
- id: check-toml
# Security
- id: detect-private-key
# Git safety
- id: check-merge-conflict
- id: check-case-conflict
- id: no-commit-to-branch
args: ['--branch=master', '--branch=main']
# File structure
- id: check-added-large-files
- id: check-symlinks
- id: check-executables-have-shebangs
- repo: local
hooks:
- id: actionlint
name: Lint GitHub Actions workflow files
description: Runs actionlint to lint GitHub Actions workflow files
language: golang
types: ["yaml"]
files: ^\.github/workflows/
entry: actionlint
minimum_pre_commit_version: 3.0.0
- repo: https://github.com/golangci/golangci-lint
rev: v2.6.1
hooks:
- id: golangci-lint
name: golangci-lint
description: Fast linters runner for Go. Note that only modified files are linted, so linters like 'unused' that need to scan all files won't work as expected.
entry: golangci-lint run --new-from-rev HEAD --fix
types: [go]
language: golang
require_serial: true
pass_filenames: false
# - id: golangci-lint-full
# name: golangci-lint-full
# description: Fast linters runner for Go. Runs on all files in the module. Use this hook if you use pre-commit in CI.
# entry: golangci-lint run --fix
# types: [go]
# language: golang
# require_serial: true
# pass_filenames: false
- id: golangci-lint-fmt
name: golangci-lint-fmt
description: Fast linters runner for Go. Formats all files in the repo.
entry: golangci-lint fmt
types: [go]
language: golang
require_serial: true
pass_filenames: false
- id: golangci-lint-config-verify
name: golangci-lint-config-verify
description: Verifies the configuration file
entry: golangci-lint config verify
files: '\.golangci\.(?:yml|yaml|toml|json)'
language: golang
pass_filenames: false

497
AGENTS.md
View File

@ -1,82 +1,36 @@
# Agent Guidelines for articulate-parser # Agent Guidelines for articulate-parser
A Go CLI tool that parses Articulate Rise courses from URLs or local JSON files and exports them to Markdown, HTML, or DOCX formats.
## Build/Test Commands ## Build/Test Commands
### Primary Commands (using Taskfile) - **Build**: `task build` or `go build -o bin/articulate-parser main.go`
- **Run tests**: `task test` or `go test -race -timeout 5m ./...`
```bash - **Run single test**: `go test -v -race -run ^TestName$ ./path/to/package`
task build # Build binary to bin/articulate-parser - **Test with coverage**:
task test # Run all tests with race detection - `task test:coverage` or
task lint # Run all linters (vet, fmt, staticcheck, golangci-lint) - `go test -race -coverprofile=coverage/coverage.out -covermode=atomic ./...`
task fmt # Format all Go files - **Lint**: `task lint` (runs vet, fmt check, staticcheck, golangci-lint)
task ci # Full CI pipeline: deps, lint, test with coverage, build - **Format**: `task fmt` or `gofmt -s -w .`
task qa # Quick QA: fmt + lint + test - **CI checks**: `task ci` (deps, lint, test with coverage, build)
```
### Direct Go Commands
```bash
# Build
go build -o bin/articulate-parser main.go
# Run all tests
go test -race -timeout 5m ./...
# Run single test by name
go test -v -race -run ^TestMarkdownExporter_Export$ ./internal/exporters
# Run tests in specific package
go test -v -race ./internal/services
# Run tests matching pattern
go test -v -race -run "TestParser" ./...
# Test with coverage
go test -race -coverprofile=coverage/coverage.out -covermode=atomic ./...
go tool cover -html=coverage/coverage.out -o coverage/coverage.html
# Benchmarks
go test -bench=. -benchmem ./...
go test -bench=BenchmarkMarkdownExporter ./internal/exporters
```
### Security & Auditing
```bash
task security:check # Run gosec security scanner
task security:audit # Run govulncheck for vulnerabilities
```
## Code Style Guidelines ## Code Style Guidelines
### Imports ### Imports
- Use `goimports` with local prefix: `github.com/kjanat/articulate-parser` - Use `goimports` with local prefix: `github.com/kjanat/articulate-parser`
- Order: stdlib, blank line, external packages, blank line, internal packages - Order: stdlib, external, internal packages
- Group related imports together
```go
import (
"context"
"fmt"
"github.com/fumiama/go-docx"
"github.com/kjanat/articulate-parser/internal/interfaces"
)
```
### Formatting ### Formatting
- Use `gofmt -s` (simplify) and `gofumpt` with extra rules - Use `gofmt -s` (simplify) and `gofumpt` with extra rules
- Function length: max 100 lines, 50 statements - Function length: max 100 lines, 50 statements
- Cyclomatic complexity: max 15; Cognitive complexity: max 20 - Cyclomatic complexity: max 15
- Cognitive complexity: max 20
### Types & Naming ### Types & Naming
- Use interface-based design (see `internal/interfaces/`) - Use interface-based design (see `internal/interfaces/`)
- Exported types/functions require godoc comments ending with period - Export types/functions with clear godoc comments ending with period
- Use descriptive names: `ArticulateParser`, `MarkdownExporter` - Use descriptive names: `ArticulateParser`, `MarkdownExporter`
- Receiver names: short (1-2 chars), consistent per type - Receiver names: short (1-2 chars), consistent per type
@ -87,20 +41,6 @@ import (
- Check all error returns (enforced by `errcheck`) - Check all error returns (enforced by `errcheck`)
- Document error handling rationale in defer blocks when ignoring close errors - Document error handling rationale in defer blocks when ignoring close errors
```go
// Good: Error wrapping with context
if err := json.Unmarshal(body, &course); err != nil {
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
}
// Good: Documented defer with error handling
defer func() {
if err := resp.Body.Close(); err != nil {
p.Logger.Warn("failed to close response body", "error", err)
}
}()
```
### Comments ### Comments
- All exported types/functions require godoc comments - All exported types/functions require godoc comments
@ -109,70 +49,379 @@ defer func() {
### Security ### Security
- Use `#nosec` with justification for deliberate security exceptions - Use `#nosec` with justification for deliberate security exceptions (G304 for CLI file paths, G306 for export file permissions)
- G304: File paths from CLI args; G306: Export file permissions - Run `gosec` and `govulncheck` for security audits
```go
// #nosec G304 - File path provided by user via CLI argument
data, err := os.ReadFile(filePath)
```
### Testing ### Testing
- Enable race detection: `-race` flag always - Enable race detection: `-race` flag
- Use table-driven tests where applicable - Use table-driven tests where applicable
- Mark test helpers with `t.Helper()` - Mark test helpers with `t.Helper()`
- Use `t.TempDir()` for temporary files
- Benchmarks in `*_bench_test.go`, examples in `*_example_test.go` - Benchmarks in `*_bench_test.go`, examples in `*_example_test.go`
- Test naming: `Test<Type>_<Method>` or `Test<Function>`
```go
func TestMarkdownExporter_ProcessItemToMarkdown_AllTypes(t *testing.T) {
tests := []struct {
name, itemType, expectedText string
}{
{"text item", "text", ""},
{"divider item", "divider", "---"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// test implementation
})
}
}
```
### Dependencies ### Dependencies
- Minimal external dependencies (go-docx, golang.org/x/net, golang.org/x/text) - Minimal external dependencies (currently: go-docx, golang.org/x/net, golang.org/x/text)
- Run `task deps:tidy` after adding/removing dependencies - Run `task deps:tidy` after adding/removing dependencies
- CGO disabled by default (`CGO_ENABLED=0`)
## Project Structure ---
``` ## Go 1.24 & 1.25 New Features Reference
articulate-parser/
internal/
config/ # Configuration loading
exporters/ # Export implementations (markdown, html, docx)
interfaces/ # Core interfaces (Exporter, CourseParser, Logger)
models/ # Data models (Course, Lesson, Item, Media)
services/ # Core services (parser, html cleaner, app, logger)
version/ # Version information
main.go # Application entry point
```
## Common Patterns This project uses Go 1.24+. Below is a comprehensive summary of new features and changes in Go 1.24 and 1.25 that may be relevant for development and maintenance.
### Creating a new exporter ### Go 1.24 Major Changes (Released February 2025)
1. Implement `interfaces.Exporter` interface #### Language Features
2. Add factory method to `internal/exporters/factory.go`
3. Register format in `NewFactory()`
4. Add tests following existing patterns
### Adding configuration options **Generic Type Aliases**
1. Add field to `Config` struct in `internal/config/config.go` - Type aliases can now be parameterized with type parameters
2. Load from environment variable with sensible default - Example: `type List[T any] = []T`
3. Document in config struct comments - Can be disabled via `GOEXPERIMENT=noaliastypeparams` (removed in 1.25)
#### Tooling Enhancements
**Module Tool Dependencies**
- New `tool` directive in go.mod tracks executable dependencies
- Use `go get -tool <package>` to add tool dependencies
- Use `go install tool` and `go get tool` to manage them
- Eliminates need for blank imports in `tools.go` files
**Build Output Formatting**
- Both `go build` and `go test` support `-json` flag for structured JSON output
- New action types distinguish build output from test results
**Authentication**
- New `GOAUTH` environment variable provides flexible authentication for private modules
**Automatic Version Tracking**
- `go build` automatically sets main module version in binaries based on VCS tags
- Adds `+dirty` suffix for uncommitted changes
**Cgo Performance Improvements**
- New `#cgo noescape` annotation: Prevents escape analysis overhead for C function calls
- New `#cgo nocallback` annotation: Indicates C function won't call back to Go
**Toolchain Tracing**
- `GODEBUG=toolchaintrace=1` enables tracing of toolchain selection
#### Runtime & Performance
**Performance Improvements**
- **2-3% CPU overhead reduction** across benchmark suites
- New Swiss Tables-based map implementation (faster lookups)
- Disable via `GOEXPERIMENT=noswissmap`
- More efficient small object allocation
- Redesigned runtime-internal mutexes
- Disable via `GOEXPERIMENT=nospinbitmutex`
#### Compiler & Linker
**Method Receiver Restrictions**
- Methods on cgo-generated types now prevented (both directly and through aliases)
**Build IDs**
- Linkers generate GNU build IDs (ELF) and UUIDs (macOS) by default
- Disable via `-B none` flag
#### Standard Library Additions
**File System Safety - `os.Root`**
- New `os.Root` type enables directory-limited operations
- Prevents path escape and symlink breakouts
- Essential for sandboxed file operations
**Cryptography Expansion**
- `crypto/mlkem`: ML-KEM-768/1024 post-quantum key exchange (FIPS 203)
- `crypto/hkdf`: HMAC-based Extract-and-Expand KDF (RFC 5869)
- `crypto/pbkdf2`: Password-based key derivation (RFC 8018)
- `crypto/sha3`: SHA-3 and SHAKE functions (FIPS 202)
**FIPS 140-3 Support**
- New `GOFIPS140` environment variable enables FIPS mode
- New `fips140` GODEBUG setting for cryptographic module compliance
**Weak References - `weak` Package**
- New `weak` package provides low-level weak pointers
- Enables memory-efficient structures like weak maps and caches
- Useful for preventing memory leaks in cache implementations
**Testing Improvements**
- `testing.B.Loop()`: Cleaner syntax replacing manual `b.N` iteration
- Prevents compiler from optimizing away benchmarked code
- New `testing/synctest` package (experimental) for testing concurrent code with fake clocks
**Iterator Support**
- Multiple packages now offer iterator-returning variants:
- `bytes`: Iterator-based functions
- `strings`: Iterator-based functions
- `go/types`: Iterator support
#### Security Enhancements
**TLS Post-Quantum Cryptography**
- `X25519MLKEM768` hybrid key exchange enabled by default in TLS
- Provides quantum-resistant security
**Encrypted Client Hello (ECH)**
- TLS servers can enable ECH via `Config.EncryptedClientHelloKeys`
- Protects client identity during TLS handshake
**RSA Key Validation**
- Keys smaller than 1024 bits now rejected by default
- Use `GODEBUG=rsa1024min=0` to revert (testing only)
**Constant-Time Execution**
- New `crypto/subtle.WithDataIndependentTiming()` enables architecture-specific timing guarantees
- Helps prevent timing attacks
#### Deprecations & Removals
- `runtime.GOROOT()`: Deprecated; use system path instead
- `crypto/cipher` OFB/CFB modes: Deprecated (unauthenticated encryption)
- `x509sha1` GODEBUG: Removed; SHA-1 certificates no longer verified
- Experimental `X25519Kyber768Draft00`: Removed
#### Platform Changes
- **Linux**: Now requires kernel 3.2+ (enforced)
- **macOS**: Go 1.24 is final release supporting Big Sur
- **Windows/ARM 32-bit**: Marked broken
- **WebAssembly**:
- New `go:wasmexport` directive
- Reactor/library builds supported via `-buildmode=c-shared`
#### Bootstrap Requirements
- Go 1.24 requires Go 1.22.6+ for bootstrapping
- Go 1.26 will require Go 1.24+
---
### Go 1.25 Major Changes (Released August 2025)
#### Language Changes
- No breaking language changes
- "Core types" concept removed from specification (replaced with clearer prose)
#### Tooling Improvements
**Go Command Enhancements**
- `go build -asan`: Now defaults to leak detection at program exit
- New `go.mod ignore` directive: Specify directories for go command to ignore
- `go doc -http`: Starts documentation server and opens in browser
- `go version -m -json`: Prints JSON-encoded BuildInfo structures
- Module path resolution now supports subdirectories using `<meta>` syntax
- New `work` package pattern matches all packages in work/workspace modules
- Removed automatic toolchain line additions when updating `go` version
**Vet Analyzers**
- **"waitgroup"**: Detects misplaced `sync.WaitGroup.Add` calls
- **"hostport"**: Warns against using `fmt.Sprintf` for constructing addresses
- Recommends `net.JoinHostPort` instead
#### Runtime Enhancements
**Container-Aware GOMAXPROCS**
- Linux now respects cgroup CPU bandwidth limits
- All OSes periodically update GOMAXPROCS if CPU availability changes
- Disable via environment variables or GODEBUG settings
- Critical for containerized applications
**New Garbage Collector - "Green Tea GC"**
- Experimental `GOEXPERIMENT=greenteagc` enables new GC
- **10-40% reduction in garbage collection overhead**
- Significant for GC-sensitive applications
**Trace Flight Recorder**
- New `runtime/trace.FlightRecorder` API
- Captures execution traces in in-memory ring buffer
- Essential for debugging rare events and production issues
**Other Runtime Changes**
- Simplified unhandled panic output
- VMA names on Linux identify memory purpose (debugging aid)
- New `SetDefaultGOMAXPROCS` function resets GOMAXPROCS to defaults
#### Compiler Fixes & Improvements
**Critical Nil Pointer Bug Fix**
- Fixed Go 1.21 regression where nil pointer checks were incorrectly delayed
- ⚠️ **May cause previously passing code to now panic** (correct behavior)
- Review code for assumptions about delayed nil checks
**DWARF5 Support**
- Debug information now uses DWARF version 5
- Reduces binary size and linking time
- Better debugging experience
**Faster Slices**
- Expanded stack allocation for slice backing stores
- Improved slice performance
#### Linker
- New `-funcalign=N` option specifies function entry alignment
#### Standard Library Highlights
**New Packages**
1. **`testing/synctest`** (Promoted from Experimental)
- Concurrent code testing with virtualized time
- Control time progression in tests
- Essential for testing time-dependent concurrent code
2. **`encoding/json/v2`** (Experimental)
- **Substantially better decoding performance**
- Improved API design
- Backward compatible with v1
**Major Package Updates**
| Package | Key Changes |
|---------|------------|
| `crypto` | New `MessageSigner` interface and `SignMessage` function |
| `crypto/ecdsa` | New raw key parsing/serialization functions |
| `crypto/rsa` | **Key generation now 3x faster** |
| `crypto/sha1` | **Hashing 2x faster on amd64 with SHA-NI** |
| `crypto/tls` | New `CurveID` field; SHA-1 algorithms disallowed in TLS 1.2 |
| `net` | Windows now supports file-to-connection conversion; IPv6 multicast improvements |
| `net/http` | **New `CrossOriginProtection` middleware for CSRF defense** |
| `os` | Windows async I/O support; `Root` type expanded with 12 new methods |
| `sync` | **New `WaitGroup.Go` method for convenient goroutine creation** |
| `testing` | New `Attr`, `Output` methods; `AllocsPerRun` panics with parallel tests |
| `unique` | More eager and parallel reclamation of interned values |
#### Performance Notes
**Performance Improvements**
- ECDSA and Ed25519 signing **4x faster** in FIPS 140-3 mode
- SHA3 hashing **2x faster** on Apple M processors
- AMD64 fused multiply-add instructions in v3+ mode
- ⚠️ **Changes floating-point results** (within IEEE 754 spec)
**Performance Regressions**
- SHA-1, SHA-256, SHA-512 slower without AVX2
- Most servers post-2015 support AVX2
#### Platform Changes
- **macOS**: Requires version 12 Monterey or later
- **Windows**: 32-bit windows/arm port marked for removal in Go 1.26
- **Loong64**: Race detector now supported
- **RISC-V**:
- Plugin build mode support
- New `GORISCV64=rva23u64` environment variable value
#### Deprecations
- `go/ast` functions: `FilterPackage`, `PackageExports`, `MergePackageFiles`
- `go/parser.ParseDir` function
- Old `testing/synctest` API (when `GOEXPERIMENT=synctest` set)
---
### Actionable Recommendations for This Project
#### Immediate Opportunities
1. **Replace `sync.WaitGroup` patterns with `WaitGroup.Go()`** (Go 1.25)
```go
// Old pattern
wg.Add(1)
go func() {
defer wg.Done()
// work
}()
// New pattern (Go 1.25)
wg.Go(func() {
// work
})
```
2. **Use `testing.B.Loop()` in benchmarks** (Go 1.24)
```go
// Old pattern
for i := 0; i < b.N; i++ {
// benchmark code
}
// New pattern (Go 1.24)
for b.Loop() {
// benchmark code
}
```
3. **Consider `os.Root` for file operations** (Go 1.24)
- Prevents path traversal vulnerabilities
- Safer for user-provided file paths
4. **Enable Green Tea GC for testing** (Go 1.25)
- Test with `GOEXPERIMENT=greenteagc`
- May reduce GC overhead by 10-40%
5. **Leverage container-aware GOMAXPROCS** (Go 1.25)
- No changes needed; automatic in containers
- Improves resource utilization
6. **Review floating-point operations** (Go 1.25)
- AMD64 v3+ uses FMA instructions
- May change floating-point results (within spec)
7. **Watch nil pointer checks** (Go 1.25)
- Compiler bug fix may expose latent nil pointer bugs
- Review crash reports carefully
#### Future Considerations
1. **Evaluate `encoding/json/v2`** when stable
- Better performance for JSON operations
- Currently experimental in Go 1.25
2. **Adopt tool directives** in go.mod
- Cleaner dependency management for build tools
- Remove `tools.go` if present
3. **Enable FIPS mode if required**
- Use `GOFIPS140=1` for compliance
- Performance improvements in Go 1.25 (4x faster signing)
4. **Use `runtime/trace.FlightRecorder`** for production debugging
- Capture traces of rare events
- Minimal overhead when not triggered

View File

@ -49,17 +49,17 @@ docker run --rm ghcr.io/kjanat/articulate-parser:latest --version
## Available Tags ## Available Tags
- `latest` - Latest stable release - `latest` - Latest stable release
- `v1.x.x` - Specific version tags - `v1.x.x` - Specific version tags
- `main` - Latest development build - `main` - Latest development build
## Image Details ## Image Details
- **Base Image**: `scratch` (minimal attack surface) - **Base Image**: `scratch` (minimal attack surface)
- **Architecture**: Multi-arch support (amd64, arm64) - **Architecture**: Multi-arch support (amd64, arm64)
- **Size**: < 10MB (optimized binary) - **Size**: < 10MB (optimized binary)
- **Security**: Runs as non-root user - **Security**: Runs as non-root user
- **Features**: SBOM and provenance attestation included - **Features**: SBOM and provenance attestation included
## Development ## Development
@ -77,6 +77,6 @@ docker-compose up --build
## Repository ## Repository
- **Source**: [github.com/kjanat/articulate-parser](https://github.com/kjanat/articulate-parser) - **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) - **Issues**: [Report bugs or request features](https://github.com/kjanat/articulate-parser/issues)
- **License**: See repository for license details - **License**: See repository for license details

170
README.md
View File

@ -78,32 +78,32 @@ flowchart TD
The system follows **Clean Architecture** principles with clear separation of concerns: The system follows **Clean Architecture** principles with clear separation of concerns:
- **🎯 Entry Point**: Command-line interface handles user input and coordinates operations - **🎯 Entry Point**: Command-line interface handles user input and coordinates operations
- **🏗️ Application Layer**: Core business logic with dependency injection - **🏗️ Application Layer**: Core business logic with dependency injection
- **📋 Interface Layer**: Contracts defining behavior without implementation details - **📋 Interface Layer**: Contracts defining behavior without implementation details
- **🔧 Service Layer**: Concrete implementations of parsing and utility services - **🔧 Service Layer**: Concrete implementations of parsing and utility services
- **📤 Export Layer**: Factory pattern for format-specific exporters - **📤 Export Layer**: Factory pattern for format-specific exporters
- **📊 Data Layer**: Domain models representing course structure - **📊 Data Layer**: Domain models representing course structure
## Features ## Features
- Parse Articulate Rise JSON data from URLs or local files - Parse Articulate Rise JSON data from URLs or local files
- Export to Markdown (.md) format - Export to Markdown (.md) format
- Export to HTML (.html) format with professional styling - Export to HTML (.html) format with professional styling
- Export to Word Document (.docx) format - Export to Word Document (.docx) format
- Support for various content types: - Support for various content types:
- Text content with headings and paragraphs - Text content with headings and paragraphs
- Lists and bullet points - Lists and bullet points
- Multimedia content (videos and images) - Multimedia content (videos and images)
- Knowledge checks and quizzes - Knowledge checks and quizzes
- Interactive content (flashcards) - Interactive content (flashcards)
- Course structure and metadata - Course structure and metadata
## Installation ## Installation
### Prerequisites ### Prerequisites
- Go, I don't know the version, but I have [![Go version](https://img.shields.io/github/go-mod/go-version/kjanat/articulate-parser?label=)][gomod] configured right now, and it works, see the [CI][Build] workflow where it is tested. - Go, I don't know the version, but I have [![Go version](https://img.shields.io/github/go-mod/go-version/kjanat/articulate-parser?label=)][gomod] configured right now, and it works, see the [CI][Build] workflow where it is tested.
### Install from source ### Install from source
@ -124,7 +124,7 @@ go install github.com/kjanat/articulate-parser@latest
The parser uses the following external library: The parser uses the following external library:
- `github.com/fumiama/go-docx` - For creating Word documents (MIT license) - `github.com/fumiama/go-docx` - For creating Word documents (MIT license)
## Testing ## Testing
@ -164,25 +164,25 @@ go run main.go <input_uri_or_file> <output_format> [output_path]
#### Examples #### Examples
1. **Parse from URL and export to Markdown:** 1. **Parse from URL and export to Markdown:**
```bash ```bash
go run main.go "https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/" md go run main.go "https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/" md
``` ```
2. **Parse from local file and export to Word:** 2. **Parse from local file and export to Word:**
```bash ```bash
go run main.go "articulate-sample.json" docx "my-course.docx" go run main.go "articulate-sample.json" docx "my-course.docx"
``` ```
3. **Parse from local file and export to HTML:** 3. **Parse from local file and export to HTML:**
```bash ```bash
go run main.go "articulate-sample.json" html "output.html" go run main.go "articulate-sample.json" html "output.html"
``` ```
4. **Parse from local file and export to Markdown:** 4. **Parse from local file and export to Markdown:**
```bash ```bash
go run main.go "articulate-sample.json" md "output.md" go run main.go "articulate-sample.json" md "output.md"
@ -332,88 +332,88 @@ The Docker image supports the following build-time arguments:
The project maintains high code quality standards: The project maintains high code quality standards:
- Cyclomatic complexity ≤ 15 (checked with [gocyclo](https://github.com/fzipp/gocyclo)) - Cyclomatic complexity ≤ 15 (checked with [gocyclo](https://github.com/fzipp/gocyclo))
- Race condition detection enabled - Race condition detection enabled
- Comprehensive test coverage - Comprehensive test coverage
- Code formatting with `gofmt` - Code formatting with `gofmt`
- Static analysis with `go vet` - Static analysis with `go vet`
### Contributing ### Contributing
1. Fork the repository 1. Fork the repository
2. Create a feature branch 2. Create a feature branch
3. Make your changes 3. Make your changes
4. Run tests: `go test ./...` 4. Run tests: `go test ./...`
5. Submit a pull request 5. Submit a pull request
## Output Formats ## Output Formats
### Markdown (`.md`) ### Markdown (`.md`)
- Hierarchical structure with proper heading levels - Hierarchical structure with proper heading levels
- Clean text content with HTML tags removed - Clean text content with HTML tags removed
- Lists and bullet points preserved - Lists and bullet points preserved
- Quiz questions with correct answers marked - Quiz questions with correct answers marked
- Media references included - Media references included
- Course metadata at the top - Course metadata at the top
### HTML (`.html`) ### HTML (`.html`)
- Professional styling with embedded CSS - Professional styling with embedded CSS
- Interactive and visually appealing layout - Interactive and visually appealing layout
- Proper HTML structure with semantic elements - Proper HTML structure with semantic elements
- Responsive design for different screen sizes - Responsive design for different screen sizes
- All content types beautifully formatted - All content types beautifully formatted
- Maintains course hierarchy and organization - Maintains course hierarchy and organization
### Word Document (`.docx`) ### Word Document (`.docx`)
- Professional document formatting - Professional document formatting
- Bold headings and proper typography - Bold headings and proper typography
- Bulleted lists - Bulleted lists
- Quiz questions with answers - Quiz questions with answers
- Media content references - Media content references
- Maintains course structure - Maintains course structure
## Supported Content Types ## Supported Content Types
The parser handles the following Articulate Rise content types: The parser handles the following Articulate Rise content types:
- **Text blocks**: Headings and paragraphs - **Text blocks**: Headings and paragraphs
- **Lists**: Bullet points and numbered lists - **Lists**: Bullet points and numbered lists
- **Multimedia**: Videos and images (references only) - **Multimedia**: Videos and images (references only)
- **Knowledge Checks**: Multiple choice, multiple response, fill-in-the-blank, matching - **Knowledge Checks**: Multiple choice, multiple response, fill-in-the-blank, matching
- **Interactive Content**: Flashcards and interactive scenarios - **Interactive Content**: Flashcards and interactive scenarios
- **Dividers**: Section breaks - **Dividers**: Section breaks
- **Sections**: Course organization - **Sections**: Course organization
## Data Structure ## Data Structure
The parser works with the standard Articulate Rise JSON format which includes: The parser works with the standard Articulate Rise JSON format which includes:
- Course metadata (title, description, settings) - Course metadata (title, description, settings)
- Lesson structure - Lesson structure
- Content items with various types - Content items with various types
- Media references - Media references
- Quiz/assessment data - Quiz/assessment data
- Styling and layout information - Styling and layout information
## URL Pattern Recognition ## URL Pattern Recognition
The parser automatically extracts share IDs from Articulate Rise URLs: The parser automatically extracts share IDs from Articulate Rise URLs:
- Input: `https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/` - Input: `https://rise.articulate.com/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO#/`
- API URL: `https://rise.articulate.com/api/rise-runtime/boot/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO` - API URL: `https://rise.articulate.com/api/rise-runtime/boot/share/N_APNg40Vr2CSH2xNz-ZLATM5kNviDIO`
## Error Handling ## Error Handling
The parser includes error handling for: The parser includes error handling for:
- Invalid URLs or share IDs - Invalid URLs or share IDs
- Network connection issues - Network connection issues
- Malformed JSON data - Malformed JSON data
- File I/O errors - File I/O errors
- Unsupported content types - Unsupported content types
<!-- ## Code coverage <!-- ## Code coverage
@ -425,28 +425,28 @@ The parser includes error handling for:
## Limitations ## Limitations
- Media files (videos, images) are referenced but not downloaded - Media files (videos, images) are referenced but not downloaded
- Complex interactive elements may be simplified in export - Complex interactive elements may be simplified in export
- Styling and visual formatting is not preserved - Styling and visual formatting is not preserved
- Assessment logic and interactivity is lost in static exports - Assessment logic and interactivity is lost in static exports
## Performance ## Performance
- Lightweight with minimal dependencies - Lightweight with minimal dependencies
- Fast JSON parsing and export - Fast JSON parsing and export
- Memory efficient processing - Memory efficient processing
- No external license requirements - No external license requirements
## Future Enhancements ## Future Enhancements
Potential improvements could include: Potential improvements could include:
- [ ] PDF export support - [ ] PDF export support
- [ ] Media file downloading - [ ] Media file downloading
- [x] ~~HTML export with preserved styling~~ - [x] ~~HTML export with preserved styling~~
- [ ] SCORM package support - [ ] SCORM package support
- [ ] Batch processing capabilities - [ ] Batch processing capabilities
- [ ] Custom template support - [ ] Custom template support
## License ## License

View File

@ -1,7 +1,7 @@
# yaml-language-server: $schema=https://taskfile.dev/schema.json # yaml-language-server: $schema=https://taskfile.dev/schema.json
# Articulate Parser - Task Automation # Articulate Parser - Task Automation
# https://taskfile.dev # https://taskfile.dev
version: "3" version: '3'
# Global output settings # Global output settings
output: prefixed output: prefixed
@ -47,11 +47,11 @@ vars:
# Environment variables # Environment variables
env: env:
CGO_ENABLED: "{{.CGO_ENABLED}}" CGO_ENABLED: '{{.CGO_ENABLED}}'
GO111MODULE: on GO111MODULE: on
# Load .env files if present # Load .env files if present
dotenv: [".env", ".env.local"] dotenv: ['.env', '.env.local']
# Task definitions # Task definitions
tasks: tasks:
@ -69,12 +69,12 @@ tasks:
interactive: true interactive: true
watch: true watch: true
sources: sources:
- "**/*.go" - '**/*.go'
- go.mod - go.mod
- go.sum - go.sum
cmds: cmds:
- task: build - task: build
- "{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} --help" - '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} --help'
# Build tasks # Build tasks
build: build:
@ -82,14 +82,14 @@ tasks:
aliases: [b] aliases: [b]
deps: [clean-bin] deps: [clean-bin]
sources: sources:
- "**/*.go" - '**/*.go'
- go.mod - go.mod
- go.sum - go.sum
generates: generates:
- "{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}}" - '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}}'
cmds: cmds:
- task: mkdir - task: mkdir
vars: { DIR: "{{.OUTPUT_DIR}}" } vars: { DIR: '{{.OUTPUT_DIR}}' }
- go build {{.GO_FLAGS}} -ldflags="{{.LDFLAGS}}" -o {{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} {{.MAIN_FILE}} - go build {{.GO_FLAGS}} -ldflags="{{.LDFLAGS}}" -o {{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} {{.MAIN_FILE}}
method: checksum method: checksum
@ -99,25 +99,25 @@ tasks:
deps: [clean-bin] deps: [clean-bin]
cmds: cmds:
- task: mkdir - task: mkdir
vars: { DIR: "{{.OUTPUT_DIR}}" } vars: { DIR: '{{.OUTPUT_DIR}}' }
- for: - for:
matrix: matrix:
GOOS: [linux, darwin, windows] GOOS: [linux, darwin, windows]
GOARCH: [amd64, arm64] GOARCH: [amd64, arm64]
task: build:platform task: build:platform
vars: vars:
TARGET_GOOS: "{{.ITEM.GOOS}}" TARGET_GOOS: '{{.ITEM.GOOS}}'
TARGET_GOARCH: "{{.ITEM.GOARCH}}" TARGET_GOARCH: '{{.ITEM.GOARCH}}'
- echo "Built binaries for all platforms in {{.OUTPUT_DIR}}/" - echo "Built binaries for all platforms in {{.OUTPUT_DIR}}/"
build:platform: build:platform:
internal: true internal: true
vars: vars:
TARGET_EXT: '{{if eq .TARGET_GOOS "windows"}}.exe{{end}}' TARGET_EXT: '{{if eq .TARGET_GOOS "windows"}}.exe{{end}}'
OUTPUT_FILE: "{{.OUTPUT_DIR}}/{{.APP_NAME}}-{{.TARGET_GOOS}}-{{.TARGET_GOARCH}}{{.TARGET_EXT}}" OUTPUT_FILE: '{{.OUTPUT_DIR}}/{{.APP_NAME}}-{{.TARGET_GOOS}}-{{.TARGET_GOARCH}}{{.TARGET_EXT}}'
env: env:
GOOS: "{{.TARGET_GOOS}}" GOOS: '{{.TARGET_GOOS}}'
GOARCH: "{{.TARGET_GOARCH}}" GOARCH: '{{.TARGET_GOARCH}}'
cmds: cmds:
- echo "Building {{.OUTPUT_FILE}}..." - echo "Building {{.OUTPUT_FILE}}..."
- go build {{.GO_FLAGS}} -ldflags="{{.LDFLAGS}}" -o "{{.OUTPUT_FILE}}" {{.MAIN_FILE}} - go build {{.GO_FLAGS}} -ldflags="{{.LDFLAGS}}" -o "{{.OUTPUT_FILE}}" {{.MAIN_FILE}}
@ -134,8 +134,6 @@ tasks:
test: test:
desc: Run all tests desc: Run all tests
aliases: [t] aliases: [t]
env:
CGO_ENABLED: 1
cmds: cmds:
- go test {{.GO_FLAGS}} -race -timeout {{.TEST_TIMEOUT}} ./... - go test {{.GO_FLAGS}} -race -timeout {{.TEST_TIMEOUT}} ./...
@ -143,11 +141,9 @@ tasks:
desc: Run tests with coverage report desc: Run tests with coverage report
aliases: [cover, cov] aliases: [cover, cov]
deps: [clean-coverage] deps: [clean-coverage]
env:
CGO_ENABLED: 1
cmds: cmds:
- task: mkdir - task: mkdir
vars: { DIR: "{{.COVERAGE_DIR}}" } vars: { DIR: '{{.COVERAGE_DIR}}' }
- go test {{.GO_FLAGS}} -race -coverprofile={{.COVERAGE_DIR}}/coverage.out -covermode=atomic -timeout {{.TEST_TIMEOUT}} ./... - 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 -html={{.COVERAGE_DIR}}/coverage.out -o {{.COVERAGE_DIR}}/coverage.html
- go tool cover -func={{.COVERAGE_DIR}}/coverage.out - go tool cover -func={{.COVERAGE_DIR}}/coverage.out
@ -156,8 +152,6 @@ tasks:
test:verbose: test:verbose:
desc: Run tests with verbose output desc: Run tests with verbose output
aliases: [tv] aliases: [tv]
env:
CGO_ENABLED: 1
cmds: cmds:
- go test -v -race -timeout {{.TEST_TIMEOUT}} ./... - go test -v -race -timeout {{.TEST_TIMEOUT}} ./...
@ -166,7 +160,7 @@ tasks:
aliases: [tw] aliases: [tw]
watch: true watch: true
sources: sources:
- "**/*.go" - '**/*.go'
cmds: cmds:
- task: test - task: test
@ -178,8 +172,6 @@ tasks:
test:integration: test:integration:
desc: Run integration tests desc: Run integration tests
env:
CGO_ENABLED: 1
status: status:
- '{{if eq OS "windows"}}if not exist "main_test.go" exit 1{{else}}test ! -f "main_test.go"{{end}}' - '{{if eq OS "windows"}}if not exist "main_test.go" exit 1{{else}}test ! -f "main_test.go"{{end}}'
cmds: cmds:
@ -324,13 +316,13 @@ tasks:
docker:run: docker:run:
desc: Run Docker container desc: Run Docker container
aliases: [dr] aliases: [dr]
deps: [docker:build] deps: ['docker:build']
cmds: cmds:
- docker run --rm {{.APP_NAME}}:{{.VERSION}} --help - docker run --rm {{.APP_NAME}}:{{.VERSION}} --help
docker:test: docker:test:
desc: Test Docker image desc: Test Docker image
deps: [docker:build] deps: ['docker:build']
cmds: cmds:
- docker run --rm {{.APP_NAME}}:{{.VERSION}} --version - docker run --rm {{.APP_NAME}}:{{.VERSION}} --version
- echo "Docker image tested successfully" - echo "Docker image tested successfully"
@ -360,14 +352,14 @@ tasks:
internal: true internal: true
cmds: cmds:
- task: rmdir - task: rmdir
vars: { DIR: "{{.OUTPUT_DIR}}" } vars: { DIR: '{{.OUTPUT_DIR}}' }
clean-coverage: clean-coverage:
desc: Remove coverage files desc: Remove coverage files
internal: true internal: true
cmds: cmds:
- task: rmdir - task: rmdir
vars: { DIR: "{{.COVERAGE_DIR}}" } vars: { DIR: '{{.COVERAGE_DIR}}' }
clean-cache: clean-cache:
desc: Clean Go build and test cache desc: Clean Go build and test cache
@ -418,10 +410,10 @@ tasks:
requires: requires:
vars: [VERSION] vars: [VERSION]
preconditions: preconditions:
- sh: "git diff --exit-code" - sh: 'git diff --exit-code'
msg: "Working directory is not clean" msg: 'Working directory is not clean'
- sh: "git diff --cached --exit-code" - sh: 'git diff --cached --exit-code'
msg: "Staging area is not clean" msg: 'Staging area is not clean'
cmds: cmds:
- git tag -a v{{.VERSION}} -m "Release v{{.VERSION}}" - git tag -a v{{.VERSION}} -m "Release v{{.VERSION}}"
- echo "Tagged v{{.VERSION}}" - echo "Tagged v{{.VERSION}}"
@ -442,7 +434,7 @@ tasks:
docs:coverage: docs:coverage:
desc: Open coverage report in browser desc: Open coverage report in browser
deps: [test:coverage] deps: ['test:coverage']
cmds: 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}}' - '{{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}}'
@ -506,11 +498,11 @@ tasks:
- '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}' - '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}'
deps: [build] deps: [build]
cmds: cmds:
- "{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json md output-demo.md" - '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json md output-demo.md'
- echo "Demo Markdown created{{:}} output-demo.md" - echo "Demo Markdown created{{:}} output-demo.md"
- defer: - defer:
task: rmfile task: rmfile
vars: { FILE: "output-demo.md" } vars: { FILE: 'output-demo.md' }
demo:html: demo:html:
desc: Demo - Convert sample to HTML desc: Demo - Convert sample to HTML
@ -518,11 +510,11 @@ tasks:
- '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}' - '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}'
deps: [build] deps: [build]
cmds: cmds:
- "{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json html output-demo.html" - '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json html output-demo.html'
- echo "Demo HTML created{{:}} output-demo.html" - echo "Demo HTML created{{:}} output-demo.html"
- defer: - defer:
task: rmfile task: rmfile
vars: { FILE: "output-demo.html" } vars: { FILE: 'output-demo.html' }
demo:docx: demo:docx:
desc: Demo - Convert sample to DOCX desc: Demo - Convert sample to DOCX
@ -530,11 +522,11 @@ tasks:
- '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}' - '{{if eq OS "windows"}}if not exist "articulate-sample.json" exit 1{{else}}test ! -f "articulate-sample.json"{{end}}'
deps: [build] deps: [build]
cmds: cmds:
- "{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json docx output-demo.docx" - '{{.OUTPUT_DIR}}/{{.APP_NAME}}{{.EXE_EXT}} articulate-sample.json docx output-demo.docx'
- echo "Demo DOCX created{{:}} output-demo.docx" - echo "Demo DOCX created{{:}} output-demo.docx"
- defer: - defer:
task: rmfile task: rmfile
vars: { FILE: "output-demo.docx" } vars: { FILE: 'output-demo.docx' }
# Performance profiling # Performance profiling
profile:cpu: profile:cpu:
@ -544,7 +536,7 @@ tasks:
- go tool pprof -http=:8080 cpu.prof - go tool pprof -http=:8080 cpu.prof
- defer: - defer:
task: rmfile task: rmfile
vars: { FILE: "cpu.prof" } vars: { FILE: 'cpu.prof' }
profile:mem: profile:mem:
desc: Run memory profiling desc: Run memory profiling
@ -553,14 +545,14 @@ tasks:
- go tool pprof -http=:8080 mem.prof - go tool pprof -http=:8080 mem.prof
- defer: - defer:
task: rmfile task: rmfile
vars: { FILE: "mem.prof" } vars: { FILE: 'mem.prof' }
# Git hooks # Git hooks
hooks:install: hooks:install:
desc: Install git hooks desc: Install git hooks
cmds: cmds:
- task: mkdir - task: mkdir
vars: { DIR: ".git/hooks" } 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}}' - '{{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" - echo "Git hooks installed"

8
go.mod
View File

@ -2,15 +2,13 @@ module github.com/kjanat/articulate-parser
go 1.24.0 go 1.24.0
toolchain go1.25.5
require ( require (
github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b
golang.org/x/net v0.48.0 golang.org/x/net v0.46.0
golang.org/x/text v0.32.0 golang.org/x/text v0.30.0
) )
require ( require (
github.com/fumiama/imgsz v0.0.4 // indirect github.com/fumiama/imgsz v0.0.4 // indirect
golang.org/x/image v0.34.0 // indirect golang.org/x/image v0.32.0 // indirect
) )

12
go.sum
View File

@ -2,9 +2,9 @@ github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b h1:/mxSugRc4SgN7Xg
github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b/go.mod h1:ssRF0IaB1hCcKIObp3FkZOsjTcAHpgii70JelNb4H8M= github.com/fumiama/go-docx v0.0.0-20250506085032-0c30fd09304b/go.mod h1:ssRF0IaB1hCcKIObp3FkZOsjTcAHpgii70JelNb4H8M=
github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI= github.com/fumiama/imgsz v0.0.4 h1:Lsasu2hdSSFS+vnD+nvR1UkiRMK7hcpyYCC0FzgSMFI=
github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E= github.com/fumiama/imgsz v0.0.4/go.mod h1:bISOQVTlw9sRytPwe8ir7tAaEmyz9hSNj9n8mXMBG0E=
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8= golang.org/x/image v0.32.0 h1:6lZQWq75h7L5IWNk0r+SCpUJ6tUVd3v4ZHnbRKLkUDQ=
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU= golang.org/x/image v0.32.0/go.mod h1:/R37rrQmKXtO6tYXAjtDLwQgFLHmhW+V6ayXlxzP2Pc=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=

View File

@ -15,7 +15,7 @@ import (
//go:embed html_styles.css //go:embed html_styles.css
var defaultCSS string var defaultCSS string
//go:embed html_template.html //go:embed html_template.gohtml
var htmlTemplate string var htmlTemplate string
// HTMLExporter implements the Exporter interface for HTML format. // HTMLExporter implements the Exporter interface for HTML format.

Binary file not shown.

View File

@ -9,4 +9,3 @@ Course description
- **Navigation Mode**: - **Navigation Mode**:
--- ---