mirror of
https://github.com/kjanat/articulate-parser.git
synced 2026-01-16 15:42:11 +01:00
Compare commits
3 Commits
33673d661b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
94a7924bed
|
|||
|
90b9d557d8
|
|||
|
33ff267644
|
42
.dprint.jsonc
Normal file
42
.dprint.jsonc
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"typescript": {
|
||||||
|
},
|
||||||
|
"json": {
|
||||||
|
},
|
||||||
|
"markdown": {
|
||||||
|
},
|
||||||
|
"toml": {
|
||||||
|
},
|
||||||
|
"dockerfile": {
|
||||||
|
},
|
||||||
|
"oxc": {
|
||||||
|
},
|
||||||
|
"ruff": {
|
||||||
|
},
|
||||||
|
"jupyter": {
|
||||||
|
},
|
||||||
|
"malva": {
|
||||||
|
},
|
||||||
|
"markup": {
|
||||||
|
},
|
||||||
|
"yaml": {
|
||||||
|
},
|
||||||
|
"excludes": [
|
||||||
|
"**/node_modules",
|
||||||
|
"**/*-lock.json",
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"https://plugins.dprint.dev/typescript-0.95.13.wasm",
|
||||||
|
"https://plugins.dprint.dev/json-0.21.1.wasm",
|
||||||
|
"https://plugins.dprint.dev/markdown-0.20.0.wasm",
|
||||||
|
"https://plugins.dprint.dev/toml-0.7.0.wasm",
|
||||||
|
"https://plugins.dprint.dev/dockerfile-0.3.3.wasm",
|
||||||
|
"https://plugins.dprint.dev/oxc-0.1.0.wasm",
|
||||||
|
"https://plugins.dprint.dev/ruff-0.6.11.wasm",
|
||||||
|
"https://plugins.dprint.dev/jupyter-0.2.1.wasm",
|
||||||
|
"https://plugins.dprint.dev/g-plane/malva-v0.15.1.wasm",
|
||||||
|
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm",
|
||||||
|
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm",
|
||||||
|
"https://plugins.dprint.dev/exec-0.6.0.json@a054130d458f124f9b5c91484833828950723a5af3f8ff2bd1523bd47b83b364",
|
||||||
|
],
|
||||||
|
}
|
||||||
20
.github/CODE_OF_CONDUCT.md
vendored
20
.github/CODE_OF_CONDUCT.md
vendored
@ -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
|
||||||
|
|||||||
130
.github/CONTRIBUTING.md
vendored
130
.github/CONTRIBUTING.md
vendored
@ -12,73 +12,73 @@ 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
|
||||||
cd articulate-parser
|
cd articulate-parser
|
||||||
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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Coding Standards
|
## Coding Standards
|
||||||
|
|
||||||
### 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
|
||||||
|
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: Bug Report
|
name: Bug Report
|
||||||
description: Create a report to help us improve
|
description: Create a report to help us improve
|
||||||
title: '[BUG] '
|
title: "[BUG] "
|
||||||
labels: ['bug', 'triage']
|
labels: ["bug", "triage"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
36
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -5,31 +5,34 @@
|
|||||||
## Related Issue
|
## Related Issue
|
||||||
|
|
||||||
<!-- Link to the issue this PR addresses using the syntax: Fixes #issue_number -->
|
<!-- Link to the issue this PR addresses using the syntax: Fixes #issue_number -->
|
||||||
|
|
||||||
Fixes #
|
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)
|
|
||||||
- [ ] New feature (non-breaking change which adds functionality)
|
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
- [ ] New feature (non-breaking change which adds functionality)
|
||||||
- [ ] Documentation update
|
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||||
- [ ] Performance improvement
|
- [ ] Documentation update
|
||||||
- [ ] Code refactoring (no functional changes)
|
- [ ] Performance improvement
|
||||||
- [ ] Test updates
|
- [ ] Code refactoring (no functional changes)
|
||||||
|
- [ ] 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
|
|
||||||
- [ ] I have performed a self-review of my code
|
- [ ] My code follows the style guidelines of this project
|
||||||
- [ ] I have added comments to complex logic
|
- [ ] I have performed a self-review of my code
|
||||||
- [ ] I have updated the documentation
|
- [ ] I have added comments to complex logic
|
||||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
- [ ] I have updated the documentation
|
||||||
- [ ] New and existing unit tests pass locally with my changes
|
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||||
- [ ] I have checked for potential breaking changes
|
- [ ] New and existing unit tests pass locally with my changes
|
||||||
- [ ] No new warnings are generated
|
- [ ] I have checked for potential breaking changes
|
||||||
- [ ] The commit message follows our guidelines
|
- [ ] No new warnings are generated
|
||||||
|
- [ ] The commit message follows our guidelines
|
||||||
|
|
||||||
## Screenshots (if appropriate)
|
## Screenshots (if appropriate)
|
||||||
|
|
||||||
@ -42,6 +45,7 @@ Fixes #
|
|||||||
## Testing Instructions
|
## Testing Instructions
|
||||||
|
|
||||||
<!-- Provide steps to test the changes, if applicable -->
|
<!-- Provide steps to test the changes, if applicable -->
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
|
|||||||
32
.github/SECURITY.md
vendored
32
.github/SECURITY.md
vendored
@ -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
|
||||||
|
|||||||
98
.github/dependabot.yml
vendored
98
.github/dependabot.yml
vendored
@ -1,86 +1,86 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
# Check for updates to GitHub Actions
|
# Check for updates to GitHub Actions
|
||||||
- package-ecosystem: 'github-actions'
|
- package-ecosystem: "github-actions"
|
||||||
directory: '/'
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'weekly'
|
interval: "weekly"
|
||||||
day: 'monday'
|
day: "monday"
|
||||||
time: '07:00'
|
time: "07:00"
|
||||||
timezone: 'Europe/Amsterdam'
|
timezone: "Europe/Amsterdam"
|
||||||
open-pull-requests-limit: 2
|
open-pull-requests-limit: 2
|
||||||
labels:
|
labels:
|
||||||
- 'dependencies'
|
- "dependencies"
|
||||||
- 'dependencies/github-actions'
|
- "dependencies/github-actions"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: 'ci'
|
prefix: "ci"
|
||||||
include: 'scope'
|
include: "scope"
|
||||||
|
|
||||||
# Check for updates to Docker
|
# Check for updates to Docker
|
||||||
- package-ecosystem: 'docker'
|
- package-ecosystem: "docker"
|
||||||
directory: '/'
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'weekly'
|
interval: "weekly"
|
||||||
day: 'monday'
|
day: "monday"
|
||||||
time: '07:00'
|
time: "07:00"
|
||||||
timezone: 'Europe/Amsterdam'
|
timezone: "Europe/Amsterdam"
|
||||||
open-pull-requests-limit: 2
|
open-pull-requests-limit: 2
|
||||||
labels:
|
labels:
|
||||||
- 'dependencies'
|
- "dependencies"
|
||||||
- 'dependencies/docker'
|
- "dependencies/docker"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: 'docker'
|
prefix: "docker"
|
||||||
include: 'scope'
|
include: "scope"
|
||||||
groups:
|
groups:
|
||||||
docker:
|
docker:
|
||||||
patterns:
|
patterns:
|
||||||
- '*'
|
- "*"
|
||||||
update-types:
|
update-types:
|
||||||
- 'minor'
|
- "minor"
|
||||||
- 'patch'
|
- "patch"
|
||||||
|
|
||||||
# Check for updates to Docker Compose
|
# Check for updates to Docker Compose
|
||||||
- package-ecosystem: 'docker-compose'
|
- package-ecosystem: "docker-compose"
|
||||||
directory: '/'
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'weekly'
|
interval: "weekly"
|
||||||
day: 'monday'
|
day: "monday"
|
||||||
time: '07:00'
|
time: "07:00"
|
||||||
timezone: 'Europe/Amsterdam'
|
timezone: "Europe/Amsterdam"
|
||||||
open-pull-requests-limit: 2
|
open-pull-requests-limit: 2
|
||||||
labels:
|
labels:
|
||||||
- 'dependencies'
|
- "dependencies"
|
||||||
- 'dependencies/docker-compose'
|
- "dependencies/docker-compose"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: 'docker'
|
prefix: "docker"
|
||||||
include: 'scope'
|
include: "scope"
|
||||||
groups:
|
groups:
|
||||||
docker:
|
docker:
|
||||||
patterns:
|
patterns:
|
||||||
- '*'
|
- "*"
|
||||||
update-types:
|
update-types:
|
||||||
- 'minor'
|
- "minor"
|
||||||
- 'patch'
|
- "patch"
|
||||||
|
|
||||||
# Check for updates to Go modules
|
# Check for updates to Go modules
|
||||||
- package-ecosystem: 'gomod'
|
- package-ecosystem: "gomod"
|
||||||
directory: '/'
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'weekly'
|
interval: "weekly"
|
||||||
day: 'monday'
|
day: "monday"
|
||||||
time: '07:00'
|
time: "07:00"
|
||||||
timezone: 'Europe/Amsterdam'
|
timezone: "Europe/Amsterdam"
|
||||||
open-pull-requests-limit: 2
|
open-pull-requests-limit: 2
|
||||||
labels:
|
labels:
|
||||||
- 'dependencies'
|
- "dependencies"
|
||||||
- 'dependencies/go'
|
- "dependencies/go"
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: 'deps'
|
prefix: "deps"
|
||||||
include: 'scope'
|
include: "scope"
|
||||||
groups:
|
groups:
|
||||||
go-modules:
|
go-modules:
|
||||||
patterns:
|
patterns:
|
||||||
- '*'
|
- "*"
|
||||||
update-types:
|
update-types:
|
||||||
- 'minor'
|
- "minor"
|
||||||
- 'patch'
|
- "patch"
|
||||||
|
|||||||
211
.github/workflows/ci.yml
vendored
211
.github/workflows/ci.yml
vendored
@ -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
|
||||||
@ -84,16 +88,17 @@ jobs:
|
|||||||
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
|
||||||
|
|
||||||
@ -101,42 +106,43 @@ jobs:
|
|||||||
|---------|--------|
|
|---------|--------|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Extract package results
|
# Extract package results
|
||||||
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
|
||||||
|
|
||||||
@ -213,52 +221,53 @@ jobs:
|
|||||||
|
|
||||||
- name: Run linters
|
- name: Run linters
|
||||||
run: |
|
run: |
|
||||||
# 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 |
|
||||||
@ -313,24 +322,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
|
||||||
@ -354,7 +365,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
needs: [test, docker-test, dependency-review]
|
needs: [test, docker-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' ||
|
||||||
@ -433,7 +444,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 }}`
|
||||||
|
|||||||
100
.github/workflows/codeql.yml
vendored
100
.github/workflows/codeql.yml
vendored
@ -17,7 +17,7 @@ name: "CodeQL"
|
|||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '44 16 * * 6'
|
- cron: "44 16 * * 6"
|
||||||
# push:
|
# push:
|
||||||
# branches: [ "master" ]
|
# branches: [ "master" ]
|
||||||
# pull_request:
|
# pull_request:
|
||||||
@ -47,58 +47,58 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- language: actions
|
- language: actions
|
||||||
build-mode: none
|
build-mode: none
|
||||||
- language: go
|
- language: go
|
||||||
build-mode: autobuild
|
build-mode: autobuild
|
||||||
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||||
# 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@v6
|
||||||
|
|
||||||
# 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`
|
||||||
# or others). This is typically only required for manual builds.
|
# or others). This is typically only required for manual builds.
|
||||||
# - name: Setup runtime (example)
|
# - name: Setup runtime (example)
|
||||||
# uses: actions/setup-example@v1
|
# uses: actions/setup-example@v1
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v4
|
uses: github/codeql-action/init@v4
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
build-mode: ${{ matrix.build-mode }}
|
build-mode: ${{ matrix.build-mode }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
# By default, queries listed here will override any specified in a config file.
|
# By default, queries listed here will override any specified in a config file.
|
||||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
# queries: security-extended,security-and-quality
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
# If the analyze step fails for one of the languages you are analyzing with
|
# If the analyze step fails for one of the languages you are analyzing with
|
||||||
# "We were unable to automatically build your code", modify the matrix above
|
# "We were unable to automatically build your code", modify the matrix above
|
||||||
# to set the build mode to "manual" for that language. Then modify this step
|
# to set the build mode to "manual" for that language. Then modify this step
|
||||||
# to build your code.
|
# to build your code.
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
- if: matrix.build-mode == 'manual'
|
- if: matrix.build-mode == 'manual'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||||
'languages you are analyzing, replace this with the commands to build' \
|
'languages you are analyzing, replace this with the commands to build' \
|
||||||
'your code, for example:'
|
'your code, for example:'
|
||||||
echo ' make bootstrap'
|
echo ' make bootstrap'
|
||||||
echo ' make release'
|
echo ' make release'
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v4
|
uses: github/codeql-action/analyze@v4
|
||||||
with:
|
with:
|
||||||
category: "/language:${{matrix.language}}"
|
category: "/language:${{matrix.language}}"
|
||||||
|
|||||||
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@ -16,10 +16,10 @@ jobs:
|
|||||||
dependency-review:
|
dependency-review:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Repository'
|
- name: "Checkout Repository"
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: 'Dependency Review'
|
- name: "Dependency Review"
|
||||||
uses: actions/dependency-review-action@v4
|
uses: actions/dependency-review-action@v4
|
||||||
with:
|
with:
|
||||||
fail-on-severity: moderate
|
fail-on-severity: moderate
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -82,7 +82,7 @@ jobs:
|
|||||||
docker:
|
docker:
|
||||||
name: Docker Build & Push
|
name: Docker Build & Push
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: ['release']
|
needs: ["release"]
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
packages: write
|
packages: write
|
||||||
|
|||||||
102
.golangci.yml
102
.golangci.yml
@ -79,68 +79,68 @@ linters:
|
|||||||
# Enable specific linters
|
# Enable specific linters
|
||||||
enable:
|
enable:
|
||||||
# Default/standard linters
|
# Default/standard linters
|
||||||
- errcheck # Check for unchecked errors
|
- errcheck # Check for unchecked errors
|
||||||
- govet # Go vet
|
- govet # Go vet
|
||||||
- ineffassign # Detect ineffectual assignments
|
- ineffassign # Detect ineffectual assignments
|
||||||
- staticcheck # Staticcheck
|
- staticcheck # Staticcheck
|
||||||
- unused # Find unused code
|
- unused # Find unused code
|
||||||
|
|
||||||
# Code quality
|
# Code quality
|
||||||
- revive # Fast, configurable linter
|
- revive # Fast, configurable linter
|
||||||
- gocritic # Opinionated Go source code linter
|
- gocritic # Opinionated Go source code linter
|
||||||
- godot # Check comment periods
|
- godot # Check comment periods
|
||||||
- godox # Detect TODO/FIXME comments
|
- godox # Detect TODO/FIXME comments
|
||||||
- gocognit # Cognitive complexity
|
- gocognit # Cognitive complexity
|
||||||
- gocyclo # Cyclomatic complexity
|
- gocyclo # Cyclomatic complexity
|
||||||
- funlen # Function length
|
- funlen # Function length
|
||||||
- maintidx # Maintainability index
|
- maintidx # Maintainability index
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
- gosec # Security problems
|
- gosec # Security problems
|
||||||
|
|
||||||
# Performance
|
# Performance
|
||||||
- prealloc # Find slice preallocation opportunities
|
- prealloc # Find slice preallocation opportunities
|
||||||
- bodyclose # Check HTTP response body closed
|
- bodyclose # Check HTTP response body closed
|
||||||
|
|
||||||
# Style and formatting
|
# Style and formatting
|
||||||
- goconst # Find repeated strings
|
- goconst # Find repeated strings
|
||||||
- misspell # Find misspellings
|
- misspell # Find misspellings
|
||||||
- whitespace # Find unnecessary blank lines
|
- whitespace # Find unnecessary blank lines
|
||||||
- unconvert # Remove unnecessary type conversions
|
- unconvert # Remove unnecessary type conversions
|
||||||
- dupword # Check for duplicate words
|
- dupword # Check for duplicate words
|
||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
- errorlint # Error handling improvements
|
- errorlint # Error handling improvements
|
||||||
- wrapcheck # Check error wrapping
|
- wrapcheck # Check error wrapping
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
- testifylint # Testify usage
|
- testifylint # Testify usage
|
||||||
- tparallel # Detect improper t.Parallel() usage
|
- tparallel # Detect improper t.Parallel() usage
|
||||||
- thelper # Detect test helpers without t.Helper()
|
- thelper # Detect test helpers without t.Helper()
|
||||||
|
|
||||||
# Best practices
|
# Best practices
|
||||||
- exhaustive # Check exhaustiveness of enum switches
|
- exhaustive # Check exhaustiveness of enum switches
|
||||||
- nolintlint # Check nolint directives
|
- nolintlint # Check nolint directives
|
||||||
- nakedret # Find naked returns
|
- nakedret # Find naked returns
|
||||||
- nilnil # Check for redundant nil checks
|
- nilnil # Check for redundant nil checks
|
||||||
- noctx # Check sending HTTP requests without context
|
- noctx # Check sending HTTP requests without context
|
||||||
- contextcheck # Check context propagation
|
- contextcheck # Check context propagation
|
||||||
- asciicheck # Check for non-ASCII identifiers
|
- asciicheck # Check for non-ASCII identifiers
|
||||||
- bidichk # Check for dangerous unicode sequences
|
- bidichk # Check for dangerous unicode sequences
|
||||||
- durationcheck # Check for multiplied durations
|
- durationcheck # Check for multiplied durations
|
||||||
- makezero # Find slice declarations with non-zero length
|
- makezero # Find slice declarations with non-zero length
|
||||||
- nilerr # Find code returning nil with non-nil error
|
- nilerr # Find code returning nil with non-nil error
|
||||||
- predeclared # Find code shadowing predeclared identifiers
|
- predeclared # Find code shadowing predeclared identifiers
|
||||||
- promlinter # Check Prometheus metrics naming
|
- promlinter # Check Prometheus metrics naming
|
||||||
- reassign # Check reassignment of package variables
|
- reassign # Check reassignment of package variables
|
||||||
- usestdlibvars # Use variables from stdlib
|
- usestdlibvars # Use variables from stdlib
|
||||||
- wastedassign # Find wasted assignments
|
- wastedassign # Find wasted assignments
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
- godoclint # Check godoc comments
|
- godoclint # Check godoc comments
|
||||||
|
|
||||||
# New
|
# New
|
||||||
- modernize # Suggest simplifications using new Go features
|
- modernize # Suggest simplifications using new Go features
|
||||||
|
|
||||||
# Exclusion rules for linters
|
# Exclusion rules for linters
|
||||||
exclusions:
|
exclusions:
|
||||||
@ -221,8 +221,8 @@ linters:
|
|||||||
govet:
|
govet:
|
||||||
enable-all: true
|
enable-all: true
|
||||||
disable:
|
disable:
|
||||||
- fieldalignment # Too many false positives
|
- fieldalignment # Too many false positives
|
||||||
- shadow # Can be noisy
|
- shadow # Can be noisy
|
||||||
|
|
||||||
# goconst settings
|
# goconst settings
|
||||||
goconst:
|
goconst:
|
||||||
@ -286,8 +286,8 @@ linters:
|
|||||||
severity: medium
|
severity: medium
|
||||||
confidence: medium
|
confidence: medium
|
||||||
excludes:
|
excludes:
|
||||||
- G104 # Handled by errcheck
|
- G104 # Handled by errcheck
|
||||||
- G304 # File path provided as taint input
|
- G304 # File path provided as taint input
|
||||||
|
|
||||||
# revive settings
|
# revive settings
|
||||||
revive:
|
revive:
|
||||||
@ -349,7 +349,15 @@ linters:
|
|||||||
|
|
||||||
# stylecheck settings
|
# stylecheck settings
|
||||||
staticcheck:
|
staticcheck:
|
||||||
checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"]
|
checks: [
|
||||||
|
"all",
|
||||||
|
"-ST1000",
|
||||||
|
"-ST1003",
|
||||||
|
"-ST1016",
|
||||||
|
"-ST1020",
|
||||||
|
"-ST1021",
|
||||||
|
"-ST1022",
|
||||||
|
]
|
||||||
|
|
||||||
# maintidx settings
|
# maintidx settings
|
||||||
maintidx:
|
maintidx:
|
||||||
|
|||||||
75
.pre-commit-config.yaml
Normal file
75
.pre-commit-config.yaml
Normal 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.7.2
|
||||||
|
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
|
||||||
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
A Go CLI tool that parses Articulate Rise courses from URLs or local JSON files and exports them to Markdown, HTML, or DOCX formats.
|
A Go CLI tool that parses Articulate Rise courses from URLs or local JSON files and exports them to Markdown, HTML, or DOCX formats.
|
||||||
|
|
||||||
|
## Repository Info
|
||||||
|
|
||||||
|
- **GitHub**: https://github.com/kjanat/articulate-parser
|
||||||
|
- **Default branch**: `master` (not `main`)
|
||||||
|
|
||||||
## Build/Test Commands
|
## Build/Test Commands
|
||||||
|
|
||||||
### Primary Commands (using Taskfile)
|
### Primary Commands (using Taskfile)
|
||||||
|
|||||||
22
DOCKER.md
22
DOCKER.md
@ -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
|
||||||
|
|||||||
198
README.md
198
README.md
@ -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 [][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 [][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"
|
||||||
@ -225,14 +225,14 @@ docker run --rm ghcr.io/kjanat/articulate-parser:latest --help
|
|||||||
|
|
||||||
### Available Tags
|
### Available Tags
|
||||||
|
|
||||||
| Tag | Description | Use Case |
|
| Tag | Description | Use Case |
|
||||||
|-----|-------------|----------|
|
| --------------------- | ------------------------------------------- | ---------------------- |
|
||||||
| `latest` | Latest stable release from master branch | Production use |
|
| `latest` | Latest stable release from master branch | Production use |
|
||||||
| `edge` | Latest development build from master branch | Testing new features |
|
| `edge` | Latest development build from master branch | Testing new features |
|
||||||
| `v1.x.x` | Specific version releases | Production pinning |
|
| `v1.x.x` | Specific version releases | Production pinning |
|
||||||
| `develop` | Development branch builds | Development/testing |
|
| `develop` | Development branch builds | Development/testing |
|
||||||
| `feature/docker-ghcr` | Feature branch builds | Feature testing |
|
| `feature/docker-ghcr` | Feature branch builds | Feature testing |
|
||||||
| `master` | Latest master branch build | Continuous integration |
|
| `master` | Latest master branch build | Continuous integration |
|
||||||
|
|
||||||
### Usage Examples
|
### Usage Examples
|
||||||
|
|
||||||
@ -313,11 +313,11 @@ docker build --build-arg VERSION=local --build-arg BUILD_TIME=$(date -u +%Y-%m-%
|
|||||||
|
|
||||||
The Docker image supports the following build-time arguments:
|
The Docker image supports the following build-time arguments:
|
||||||
|
|
||||||
| Argument | Description | Default |
|
| Argument | Description | Default |
|
||||||
|----------|-------------|---------|
|
| ------------ | ------------------------------------- | -------------- |
|
||||||
| `VERSION` | Version string embedded in the binary | `dev` |
|
| `VERSION` | Version string embedded in the binary | `dev` |
|
||||||
| `BUILD_TIME` | Build timestamp | Current time |
|
| `BUILD_TIME` | Build timestamp | Current time |
|
||||||
| `GIT_COMMIT` | Git commit hash | Current commit |
|
| `GIT_COMMIT` | Git commit hash | Current commit |
|
||||||
|
|
||||||
### Docker Security
|
### Docker Security
|
||||||
|
|
||||||
@ -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
|
||||||
|
|
||||||
@ -460,7 +460,9 @@ This is a utility tool for educational content conversion. Please ensure you hav
|
|||||||
[Go report]: https://goreportcard.com/report/github.com/kjanat/articulate-parser
|
[Go report]: https://goreportcard.com/report/github.com/kjanat/articulate-parser
|
||||||
[gomod]: go.mod
|
[gomod]: go.mod
|
||||||
[Issues]: https://github.com/kjanat/articulate-parser/issues
|
[Issues]: https://github.com/kjanat/articulate-parser/issues
|
||||||
|
|
||||||
<!-- [Latest release]: https://github.com/kjanat/articulate-parser/releases/latest -->
|
<!-- [Latest release]: https://github.com/kjanat/articulate-parser/releases/latest -->
|
||||||
|
|
||||||
[MIT License]: LICENSE
|
[MIT License]: LICENSE
|
||||||
[Package documentation]: https://godoc.org/github.com/kjanat/articulate-parser
|
[Package documentation]: https://godoc.org/github.com/kjanat/articulate-parser
|
||||||
[Tags]: https://github.com/kjanat/articulate-parser/tags
|
[Tags]: https://github.com/kjanat/articulate-parser/tags
|
||||||
|
|||||||
34
Taskfile.yml
34
Taskfile.yml
@ -210,13 +210,13 @@ tasks:
|
|||||||
sh: gofmt -s -l .
|
sh: gofmt -s -l .
|
||||||
cmds:
|
cmds:
|
||||||
- |
|
- |
|
||||||
{{if ne .UNFORMATTED ""}}
|
{{if ne .UNFORMATTED ""}}
|
||||||
echo "❌ The following files need formatting:"
|
echo "❌ The following files need formatting:"
|
||||||
echo "{{.UNFORMATTED}}"
|
echo "{{.UNFORMATTED}}"
|
||||||
exit 1
|
exit 1
|
||||||
{{else}}
|
{{else}}
|
||||||
echo "All files are properly formatted"
|
echo "All files are properly formatted"
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
lint:staticcheck:
|
lint:staticcheck:
|
||||||
desc: Run staticcheck (install if needed)
|
desc: Run staticcheck (install if needed)
|
||||||
@ -304,22 +304,22 @@ tasks:
|
|||||||
aliases: [db]
|
aliases: [db]
|
||||||
cmds:
|
cmds:
|
||||||
- |
|
- |
|
||||||
docker build \
|
docker build \
|
||||||
--build-arg VERSION={{.VERSION}} \
|
--build-arg VERSION={{.VERSION}} \
|
||||||
--build-arg BUILD_TIME={{.BUILD_TIME}} \
|
--build-arg BUILD_TIME={{.BUILD_TIME}} \
|
||||||
--build-arg GIT_COMMIT={{.GIT_COMMIT}} \
|
--build-arg GIT_COMMIT={{.GIT_COMMIT}} \
|
||||||
-t {{.APP_NAME}}:{{.VERSION}} \
|
-t {{.APP_NAME}}:{{.VERSION}} \
|
||||||
-t {{.APP_NAME}}:latest \
|
-t {{.APP_NAME}}:latest \
|
||||||
.
|
.
|
||||||
- >
|
- >
|
||||||
echo "Docker image built: {{.APP_NAME}}:{{.VERSION}}"
|
echo "Docker image built: {{.APP_NAME}}:{{.VERSION}}"
|
||||||
|
|
||||||
docker:build:dev:
|
docker:build:dev:
|
||||||
desc: Build development Docker image
|
desc: Build development Docker image
|
||||||
cmds:
|
cmds:
|
||||||
- docker build -f Dockerfile.dev -t {{.APP_NAME}}:dev .
|
- docker build -f Dockerfile.dev -t {{.APP_NAME}}:dev .
|
||||||
- >
|
- >
|
||||||
echo "Development Docker image built: {{.APP_NAME}}:dev"
|
echo "Development Docker image built: {{.APP_NAME}}:dev"
|
||||||
|
|
||||||
docker:run:
|
docker:run:
|
||||||
desc: Run Docker container
|
desc: Run Docker container
|
||||||
@ -426,7 +426,7 @@ tasks:
|
|||||||
- 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}}"
|
||||||
- >
|
- >
|
||||||
echo "Push with: git push origin v{{.VERSION}}"
|
echo "Push with: git push origin v{{.VERSION}}"
|
||||||
|
|
||||||
# Documentation tasks
|
# Documentation tasks
|
||||||
docs:serve:
|
docs:serve:
|
||||||
|
|||||||
@ -16,6 +16,13 @@ import (
|
|||||||
"github.com/kjanat/articulate-parser/internal/services"
|
"github.com/kjanat/articulate-parser/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Font sizes for DOCX document headings (in half-points, so "32" = 16pt).
|
||||||
|
const (
|
||||||
|
docxTitleSize = "32" // Course title (16pt)
|
||||||
|
docxLessonSize = "28" // Lesson heading (14pt)
|
||||||
|
docxItemSize = "24" // Item heading (12pt)
|
||||||
|
)
|
||||||
|
|
||||||
// DocxExporter implements the Exporter interface for DOCX format.
|
// DocxExporter implements the Exporter interface for DOCX format.
|
||||||
// It converts Articulate Rise course data into a Microsoft Word document
|
// It converts Articulate Rise course data into a Microsoft Word document
|
||||||
// using the go-docx package.
|
// using the go-docx package.
|
||||||
@ -53,7 +60,7 @@ func (e *DocxExporter) Export(course *models.Course, outputPath string) error {
|
|||||||
|
|
||||||
// Add title
|
// Add title
|
||||||
titlePara := doc.AddParagraph()
|
titlePara := doc.AddParagraph()
|
||||||
titlePara.AddText(course.Course.Title).Size("32").Bold()
|
titlePara.AddText(course.Course.Title).Size(docxTitleSize).Bold()
|
||||||
|
|
||||||
// Add description if available
|
// Add description if available
|
||||||
if course.Course.Description != "" {
|
if course.Course.Description != "" {
|
||||||
@ -106,7 +113,7 @@ func (e *DocxExporter) Export(course *models.Course, outputPath string) error {
|
|||||||
func (e *DocxExporter) exportLesson(doc *docx.Docx, lesson *models.Lesson) {
|
func (e *DocxExporter) exportLesson(doc *docx.Docx, lesson *models.Lesson) {
|
||||||
// Add lesson title
|
// Add lesson title
|
||||||
lessonPara := doc.AddParagraph()
|
lessonPara := doc.AddParagraph()
|
||||||
lessonPara.AddText(fmt.Sprintf("Lesson: %s", lesson.Title)).Size("28").Bold()
|
lessonPara.AddText(fmt.Sprintf("Lesson: %s", lesson.Title)).Size(docxLessonSize).Bold()
|
||||||
|
|
||||||
// Add lesson description if available
|
// Add lesson description if available
|
||||||
if lesson.Description != "" {
|
if lesson.Description != "" {
|
||||||
@ -132,7 +139,7 @@ func (e *DocxExporter) exportItem(doc *docx.Docx, item *models.Item) {
|
|||||||
if item.Type != "" {
|
if item.Type != "" {
|
||||||
itemPara := doc.AddParagraph()
|
itemPara := doc.AddParagraph()
|
||||||
caser := cases.Title(language.English)
|
caser := cases.Title(language.English)
|
||||||
itemPara.AddText(caser.String(item.Type)).Size("24").Bold()
|
itemPara.AddText(caser.String(item.Type)).Size(docxItemSize).Bold()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add sub-items
|
// Add sub-items
|
||||||
|
|||||||
@ -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.
|
||||||
@ -69,7 +69,16 @@ func (e *HTMLExporter) Export(course *models.Course, outputPath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create file: %w", err)
|
return fmt.Errorf("failed to create file: %w", err)
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer func() {
|
||||||
|
// Close errors are logged but not fatal since the content has already been written.
|
||||||
|
// The file must be closed to flush buffers, but a close error doesn't invalidate
|
||||||
|
// the data already written to disk.
|
||||||
|
if closeErr := f.Close(); closeErr != nil {
|
||||||
|
// Note: In production, this should log via a logger passed to the exporter.
|
||||||
|
// For now, we silently ignore close errors as they're non-fatal.
|
||||||
|
_ = closeErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return e.WriteHTML(f, course)
|
return e.WriteHTML(f, course)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,173 +1,175 @@
|
|||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
font-family:
|
||||||
line-height: 1.6;
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
|
||||||
color: #333;
|
Cantarell, sans-serif;
|
||||||
max-width: 800px;
|
line-height: 1.6;
|
||||||
margin: 0 auto;
|
color: #333;
|
||||||
padding: 20px;
|
max-width: 800px;
|
||||||
background-color: #f9f9f9;
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
header {
|
header {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
header h1 {
|
header h1 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
}
|
}
|
||||||
.course-description {
|
.course-description {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
.course-info {
|
.course-info {
|
||||||
background: white;
|
background: white;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
.course-info h2 {
|
.course-info h2 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #4a5568;
|
color: #4a5568;
|
||||||
border-bottom: 2px solid #e2e8f0;
|
border-bottom: 2px solid #e2e8f0;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.course-info ul {
|
.course-info ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
.course-info li {
|
.course-info li {
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
background: #f7fafc;
|
background: #f7fafc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.course-section {
|
.course-section {
|
||||||
background: #4299e1;
|
background: #4299e1;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
.course-section h2 {
|
.course-section h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
.lesson {
|
.lesson {
|
||||||
background: white;
|
background: white;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
border-left: 4px solid #4299e1;
|
border-left: 4px solid #4299e1;
|
||||||
}
|
}
|
||||||
.lesson h3 {
|
.lesson h3 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #2d3748;
|
color: #2d3748;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
.lesson-description {
|
.lesson-description {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #f7fafc;
|
background: #f7fafc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-left: 3px solid #4299e1;
|
border-left: 3px solid #4299e1;
|
||||||
}
|
}
|
||||||
.item {
|
.item {
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #e2e8f0;
|
||||||
}
|
}
|
||||||
.item h4 {
|
.item h4 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #4a5568;
|
color: #4a5568;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
.text-item {
|
.text-item {
|
||||||
background: #f0fff4;
|
background: #f0fff4;
|
||||||
border-left: 3px solid #48bb78;
|
border-left: 3px solid #48bb78;
|
||||||
}
|
}
|
||||||
.list-item {
|
.list-item {
|
||||||
background: #fffaf0;
|
background: #fffaf0;
|
||||||
border-left: 3px solid #ed8936;
|
border-left: 3px solid #ed8936;
|
||||||
}
|
}
|
||||||
.knowledge-check {
|
.knowledge-check {
|
||||||
background: #e6fffa;
|
background: #e6fffa;
|
||||||
border-left: 3px solid #38b2ac;
|
border-left: 3px solid #38b2ac;
|
||||||
}
|
}
|
||||||
.multimedia-item {
|
.multimedia-item {
|
||||||
background: #faf5ff;
|
background: #faf5ff;
|
||||||
border-left: 3px solid #9f7aea;
|
border-left: 3px solid #9f7aea;
|
||||||
}
|
}
|
||||||
.interactive-item {
|
.interactive-item {
|
||||||
background: #fff5f5;
|
background: #fff5f5;
|
||||||
border-left: 3px solid #f56565;
|
border-left: 3px solid #f56565;
|
||||||
}
|
}
|
||||||
.unknown-item {
|
.unknown-item {
|
||||||
background: #f7fafc;
|
background: #f7fafc;
|
||||||
border-left: 3px solid #a0aec0;
|
border-left: 3px solid #a0aec0;
|
||||||
}
|
}
|
||||||
.answers {
|
.answers {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
.answers h5 {
|
.answers h5 {
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
color: #4a5568;
|
color: #4a5568;
|
||||||
}
|
}
|
||||||
.answers ol {
|
.answers ol {
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
}
|
}
|
||||||
.answers li {
|
.answers li {
|
||||||
margin: 0.3rem 0;
|
margin: 0.3rem 0;
|
||||||
padding: 0.3rem;
|
padding: 0.3rem;
|
||||||
}
|
}
|
||||||
.correct-answer {
|
.correct-answer {
|
||||||
background: #c6f6d5;
|
background: #c6f6d5;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.correct-answer::after {
|
.correct-answer::after {
|
||||||
content: " ✓";
|
content: " ✓";
|
||||||
color: #38a169;
|
color: #38a169;
|
||||||
}
|
}
|
||||||
.feedback {
|
.feedback {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #edf2f7;
|
background: #edf2f7;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-left: 3px solid #4299e1;
|
border-left: 3px solid #4299e1;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.media-info {
|
.media-info {
|
||||||
background: #edf2f7;
|
background: #edf2f7;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
.media-info strong {
|
.media-info strong {
|
||||||
color: #4a5568;
|
color: #4a5568;
|
||||||
}
|
}
|
||||||
hr {
|
hr {
|
||||||
border: none;
|
border: none;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background: linear-gradient(to right, #667eea, #764ba2);
|
background: linear-gradient(to right, #667eea, #764ba2);
|
||||||
margin: 2rem 0;
|
margin: 2rem 0;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
}
|
}
|
||||||
ul {
|
ul {
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
}
|
}
|
||||||
li {
|
li {
|
||||||
margin: 0.5rem 0;
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -96,7 +96,10 @@ func (e *MarkdownExporter) SupportedFormat() string {
|
|||||||
func (e *MarkdownExporter) processItemToMarkdown(buf *bytes.Buffer, item models.Item, level int) {
|
func (e *MarkdownExporter) processItemToMarkdown(buf *bytes.Buffer, item models.Item, level int) {
|
||||||
headingPrefix := strings.Repeat("#", level)
|
headingPrefix := strings.Repeat("#", level)
|
||||||
|
|
||||||
switch item.Type {
|
// Normalize item type to lowercase for consistent matching
|
||||||
|
itemType := strings.ToLower(item.Type)
|
||||||
|
|
||||||
|
switch itemType {
|
||||||
case "text":
|
case "text":
|
||||||
e.processTextItem(buf, item, headingPrefix)
|
e.processTextItem(buf, item, headingPrefix)
|
||||||
case "list":
|
case "list":
|
||||||
@ -105,7 +108,7 @@ func (e *MarkdownExporter) processItemToMarkdown(buf *bytes.Buffer, item models.
|
|||||||
e.processMultimediaItem(buf, item, headingPrefix)
|
e.processMultimediaItem(buf, item, headingPrefix)
|
||||||
case "image":
|
case "image":
|
||||||
e.processImageItem(buf, item, headingPrefix)
|
e.processImageItem(buf, item, headingPrefix)
|
||||||
case "knowledgeCheck":
|
case "knowledgecheck":
|
||||||
e.processKnowledgeCheckItem(buf, item, headingPrefix)
|
e.processKnowledgeCheckItem(buf, item, headingPrefix)
|
||||||
case "interactive":
|
case "interactive":
|
||||||
e.processInteractiveItem(buf, item, headingPrefix)
|
e.processInteractiveItem(buf, item, headingPrefix)
|
||||||
|
|||||||
Binary file not shown.
@ -15,6 +15,9 @@ import (
|
|||||||
"github.com/kjanat/articulate-parser/internal/models"
|
"github.com/kjanat/articulate-parser/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// shareIDRegex is compiled once at package init for extracting share IDs from URIs.
|
||||||
|
var shareIDRegex = regexp.MustCompile(`/share/([a-zA-Z0-9_-]+)`)
|
||||||
|
|
||||||
// ArticulateParser implements the CourseParser interface specifically for Articulate Rise courses.
|
// ArticulateParser implements the CourseParser interface specifically for Articulate Rise courses.
|
||||||
// It can fetch courses from the Articulate Rise API or load them from local JSON files.
|
// It can fetch courses from the Articulate Rise API or load them from local JSON files.
|
||||||
type ArticulateParser struct {
|
type ArticulateParser struct {
|
||||||
@ -78,15 +81,15 @@ func (p *ArticulateParser) FetchCourse(ctx context.Context, uri string) (*models
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
var course models.Course
|
var course models.Course
|
||||||
if err := json.Unmarshal(body, &course); err != nil {
|
if err := json.Unmarshal(body, &course); err != nil {
|
||||||
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
|
||||||
@ -133,8 +136,7 @@ func (p *ArticulateParser) extractShareID(uri string) (string, error) {
|
|||||||
return "", fmt.Errorf("invalid domain for Articulate Rise URI: %s", parsedURL.Host)
|
return "", fmt.Errorf("invalid domain for Articulate Rise URI: %s", parsedURL.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
re := regexp.MustCompile(`/share/([a-zA-Z0-9_-]+)`)
|
matches := shareIDRegex.FindStringSubmatch(uri)
|
||||||
matches := re.FindStringSubmatch(uri)
|
|
||||||
if len(matches) < 2 {
|
if len(matches) < 2 {
|
||||||
return "", fmt.Errorf("could not extract share ID from URI: %s", uri)
|
return "", fmt.Errorf("could not extract share ID from URI: %s", uri)
|
||||||
}
|
}
|
||||||
|
|||||||
2
main.go
2
main.go
@ -92,7 +92,7 @@ func run(args []string) int {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - true if the string appears to be a URI, false otherwise
|
// - true if the string appears to be a URI, false otherwise
|
||||||
func isURI(str string) bool {
|
func isURI(str string) bool {
|
||||||
return len(str) > 7 && (str[:7] == "http://" || str[:8] == "https://")
|
return strings.HasPrefix(str, "http://") || strings.HasPrefix(str, "https://")
|
||||||
}
|
}
|
||||||
|
|
||||||
// printUsage prints the command-line usage information.
|
// printUsage prints the command-line usage information.
|
||||||
|
|||||||
Reference in New Issue
Block a user