Compare commits
23 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b80ac51326 | ||
|
|
7c2131fe24 | ||
|
|
5be3a1d4e2 | ||
|
|
0239b0cab2 | ||
|
|
fc3549d722 | ||
|
|
0a6e490673 | ||
|
|
87608426bf | ||
|
|
c5198ed241 | ||
|
|
e84340df2c | ||
|
|
5bbe5cedfe | ||
|
|
14cc0a7d6a | ||
|
|
643281dfb3 | ||
|
|
3aa121d9da | ||
|
|
dc28110856 | ||
|
|
ab9ba73e82 | ||
|
|
01e283f96b | ||
|
|
660fc317ab | ||
|
|
ababaa3ff9 | ||
|
|
72ac992aa0 | ||
|
|
31b2514596 | ||
|
|
416b478237 | ||
|
|
ad19c98615 | ||
|
|
1a2a37825c |
92 changed files with 10715 additions and 204 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
.crush
|
||||
CRUSH.md
|
||||
config.yml
|
||||
|
||||
dist/
|
||||
|
|
|
|||
136
.goreleaser.yml
Normal file
136
.goreleaser.yml
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
version: 2
|
||||
|
||||
project_name: scron
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
- go generate ./...
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- "386"
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
main: ./
|
||||
binary: scron
|
||||
ldflags:
|
||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
|
||||
|
||||
|
||||
nfpms:
|
||||
- id: scron
|
||||
package_name: scron
|
||||
vendor: SCRON Project
|
||||
homepage: https://git.keks.cloud/kekskurse/scron
|
||||
maintainer: SCRON Maintainers <maintainer@example.com>
|
||||
description: |-
|
||||
A lightweight, efficient cron-like scheduler written in Go.
|
||||
SCRON executes shell commands at specified intervals with precise timing
|
||||
and comprehensive logging support.
|
||||
license: MIT
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
- apk
|
||||
bindir: /usr/bin
|
||||
section: utils
|
||||
priority: optional
|
||||
file_name_template: "{{ .ConventionalFileName }}"
|
||||
contents:
|
||||
- src: config.yml.example
|
||||
dst: /etc/scron/config.yml
|
||||
type: config|noreplace
|
||||
file_info:
|
||||
mode: 0644
|
||||
- src: scripts/scron.service
|
||||
dst: /etc/systemd/system/scron.service
|
||||
file_info:
|
||||
mode: 0644
|
||||
- dst: /etc/scron
|
||||
type: dir
|
||||
file_info:
|
||||
mode: 0755
|
||||
scripts:
|
||||
postinstall: scripts/postinstall.sh
|
||||
preremove: scripts/preremove.sh
|
||||
postremove: scripts/postremove.sh
|
||||
dependencies:
|
||||
- bash
|
||||
- systemd
|
||||
recommends:
|
||||
- logrotate
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
use: github
|
||||
filters:
|
||||
exclude:
|
||||
- "^docs:"
|
||||
- "^test:"
|
||||
- "^ci:"
|
||||
- "^chore:"
|
||||
- "^style:"
|
||||
groups:
|
||||
- title: Features
|
||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$'
|
||||
order: 0
|
||||
- title: 'Bug fixes'
|
||||
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$'
|
||||
order: 1
|
||||
- title: 'Performance improvements'
|
||||
regexp: '^.*?perf(\([[:word:]]+\))??!?:.+$'
|
||||
order: 2
|
||||
- title: Others
|
||||
order: 999
|
||||
|
||||
gitea_urls:
|
||||
api: https://git.keks.cloud/api/v1/
|
||||
download: https://git.keks.cloud
|
||||
skip_tls_verify: false
|
||||
|
||||
release:
|
||||
gitea:
|
||||
owner: kekskurse
|
||||
name: scron
|
||||
draft: false
|
||||
prerelease: auto
|
||||
mode: replace
|
||||
header: |
|
||||
## SCRON {{ .Tag }}
|
||||
|
||||
A lightweight, efficient cron-like scheduler written in Go.
|
||||
footer: |
|
||||
## Installation
|
||||
|
||||
### Binary Installation
|
||||
Download the appropriate binary for your system from the assets below.
|
||||
|
||||
```bash
|
||||
sudo dpkg -i scron_{{ .Version }}_linux_amd64.deb
|
||||
```
|
||||
|
||||
### RPM-based systems (.rpm package)
|
||||
```bash
|
||||
sudo rpm -i scron_{{ .Version }}_linux_amd64.rpm
|
||||
```
|
||||
|
||||
### Alpine Linux (.apk package)
|
||||
```bash
|
||||
sudo apk add --allow-untrusted scron_{{ .Version }}_linux_amd64.apk
|
||||
```
|
||||
|
||||
name_template: "{{.ProjectName}}-v{{.Version}}"
|
||||
|
|
@ -1,10 +1,24 @@
|
|||
when:
|
||||
- event: push
|
||||
- event: pull_request
|
||||
- event: tag
|
||||
|
||||
steps:
|
||||
test:
|
||||
image: golang:1.24-alpine
|
||||
commands:
|
||||
- go mod download
|
||||
- go test ./...
|
||||
- go test ./...
|
||||
when:
|
||||
- event: [push, pull_request]
|
||||
|
||||
release:
|
||||
image: goreleaser/goreleaser:latest
|
||||
commands:
|
||||
- goreleaser release --clean
|
||||
environment:
|
||||
GITEA_TOKEN:
|
||||
from_secret: gitea_token
|
||||
when:
|
||||
- event: tag
|
||||
|
||||
|
|
|
|||
472
README.md
472
README.md
|
|
@ -1,214 +1,344 @@
|
|||
# SCRON
|
||||
|
||||
A lightweight, efficient cron-like scheduler written in Go that executes shell commands at specified intervals with precise timing and comprehensive logging.
|
||||
A lightweight, efficient cron-like scheduler written in Go that executes shell commands at specified intervals with precise timing, comprehensive logging, and flexible notification support.
|
||||
|
||||
## Features
|
||||
|
||||
- **Flexible Scheduling**: Schedule jobs using minute and hour patterns with wildcard support
|
||||
- **Precise Timing**: Executes at exact minute boundaries (second 0) for reliable scheduling
|
||||
- **Configuration Reloading**: Monitors config file changes for dynamic job updates (every 50 seconds)
|
||||
- **Comprehensive Logging**: Detailed execution logs using `zerolog` with configurable levels
|
||||
- **Error Handling**: Robust error handling with exit code tracking and output capture
|
||||
- **Shell Command Support**: Execute any shell command via bash
|
||||
- **🕐 Flexible Scheduling**: Advanced cron-like time patterns with minute-level precision
|
||||
- **🔄 Dynamic Configuration**: Automatic config reloading without restart
|
||||
- **📊 Structured Logging**: JSON-formatted logs with zerolog for easy parsing
|
||||
- **🔔 Multi-Channel Notifications**: Support for Gotify and SMTP notifications
|
||||
- **⚡ Concurrent Execution**: Jobs run in parallel for optimal performance
|
||||
- **🛡️ Robust Error Handling**: Comprehensive error tracking with exit codes
|
||||
- **📝 Output Capture**: Combined stdout/stderr capture for debugging
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
- Go 1.24.1 or later
|
||||
### From Source
|
||||
|
||||
### Build from Source
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
# Clone the repository
|
||||
git clone https://github.com/yourusername/scron.git
|
||||
cd scron
|
||||
|
||||
# Build the binary
|
||||
go build -o scron ./
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Create a `config.yml` file in the same directory as the binary:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "Daily Backup"
|
||||
minute: "0"
|
||||
hour: "2"
|
||||
command: "backup.sh /data /backup"
|
||||
|
||||
- name: "Health Check"
|
||||
minute: "*/5"
|
||||
hour: "*"
|
||||
command: "curl -f http://localhost:8080/health"
|
||||
|
||||
- name: "Log Cleanup"
|
||||
minute: "30"
|
||||
hour: "1"
|
||||
command: "find /var/log -name '*.log' -mtime +7 -delete"
|
||||
```
|
||||
|
||||
### Configuration Fields
|
||||
|
||||
| Field | Description | Format | Examples |
|
||||
|-------|-------------|--------|----------|
|
||||
| `name` | Descriptive job name for logging | String | "Daily Backup", "Health Check" |
|
||||
| `minute` | Minute pattern (0-59) | String | `"0"`, `"*/5"`, `"15,30,45"`, `"*"` |
|
||||
| `hour` | Hour pattern (0-23) | String | `"2"`, `"*/2"`, `"9-17"`, `"*"` |
|
||||
| `command` | Shell command to execute | String | `"echo 'Hello'"`, `"backup.sh"` |
|
||||
|
||||
### Pattern Syntax
|
||||
- `*` - Every minute/hour
|
||||
- `*/n` - Every n minutes/hours
|
||||
- `n` - Specific minute/hour
|
||||
- `n,m,o` - Multiple specific values
|
||||
- `n-m` - Range of values
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
```bash
|
||||
# Run
|
||||
./scron
|
||||
```
|
||||
|
||||
### With Debug Logging
|
||||
### Debian Package
|
||||
|
||||
Download the latest `.deb` package from the [releases page](https://git.keks.cloud/kekskurse/scron/releases):
|
||||
|
||||
```bash
|
||||
ZEROLOG_LEVEL=debug ./scron
|
||||
# Install package
|
||||
sudo dpkg -i scron_X.X.X_linux_amd64.deb
|
||||
|
||||
```
|
||||
|
||||
### Running as a Service
|
||||
```bash
|
||||
# systemd service example
|
||||
sudo cp scron /usr/local/bin/
|
||||
sudo cp config.yml /etc/scron/
|
||||
# Create systemd service file...
|
||||
Configuration file location: `/etc/scron/config.yml`
|
||||
|
||||
## Configuration
|
||||
|
||||
Create a `config.yml` file in the same directory as the binary (or `/etc/scron/` for package installations):
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
Optionally, you can specify the `weekday` field (0=Sunday, 1=Monday, ..., 6=Saturday) to restrict jobs to specific days of the week.
|
||||
|
||||
You can also optionally specify the `timezone` field to set the timezone for each job. Default is UTC. You can use any timezone supported by Go (e.g., `Europe/Berlin`).
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "System Health Check"
|
||||
minute: "*/5"
|
||||
hour: "*"
|
||||
weekday: "*"
|
||||
timezone: "Europe/Berlin"
|
||||
command: "curl -f http://localhost:8080/health || exit 1"
|
||||
|
||||
- name: "Daily Backup"
|
||||
minute: "0"
|
||||
hour: "2"
|
||||
weekday: "*"
|
||||
command: "/usr/local/bin/backup.sh"
|
||||
```
|
||||
|
||||
## Development
|
||||
### Advanced Configuration with Notifications
|
||||
|
||||
### Testing
|
||||
```bash
|
||||
# Run all tests
|
||||
go test ./...
|
||||
```yaml
|
||||
# Notification configurations
|
||||
notification:
|
||||
- name: default
|
||||
success:
|
||||
gotify:
|
||||
url: "https://gotify.example.com/message?token=YOUR_TOKEN"
|
||||
smtp:
|
||||
host: "smtp.gmail.com"
|
||||
port: 587
|
||||
username: "your-email@gmail.com"
|
||||
password: "your-app-password"
|
||||
from: "scron@example.com"
|
||||
to: "admin@example.com"
|
||||
use_ssl: true
|
||||
error:
|
||||
gotify:
|
||||
url: "https://gotify.example.com/message?token=YOUR_CRITICAL_TOKEN"
|
||||
smtp:
|
||||
host: "smtp.gmail.com"
|
||||
port: 587
|
||||
username: "your-email@gmail.com"
|
||||
password: "your-app-password"
|
||||
from: "scron-alerts@example.com"
|
||||
to: "oncall@example.com"
|
||||
use_ssl: true
|
||||
|
||||
# Run specific test
|
||||
go test -run ^TestConfigParsing$ ./
|
||||
- name: critical-only
|
||||
error:
|
||||
gotify:
|
||||
url: "https://gotify.example.com/message?token=YOUR_CRITICAL_TOKEN"
|
||||
|
||||
# Run tests with coverage
|
||||
go test -cover ./...
|
||||
# Job definitions
|
||||
jobs:
|
||||
- name: "Database Backup"
|
||||
minute: "0"
|
||||
hour: "3"
|
||||
weekday: "*"
|
||||
command: "pg_dump mydb > /backup/mydb_$(date +%Y%m%d).sql"
|
||||
notification: default
|
||||
|
||||
- name: "Critical Service Monitor"
|
||||
minute: "*/1"
|
||||
hour: "*"
|
||||
weekday: "*"
|
||||
command: "systemctl is-active nginx || exit 1"
|
||||
notification: critical-only
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
```bash
|
||||
# Format and vet code
|
||||
go fmt ./... && go vet ./...
|
||||
## Time Pattern Syntax (minute, hour, and optional weekday)
|
||||
|
||||
# Run linter (if golangci-lint is installed)
|
||||
golangci-lint run
|
||||
SCRON supports flexible cron-like time patterns for minutes (0-59), hours (0-23), and optional weekdays (0-6). 0 is Sunday, 1 is Monday, and so on:
|
||||
|
||||
| Pattern | Description | Example |
|
||||
|---------|-------------|---------|
|
||||
| `*` | Every minute/hour | `minute: "*"` - every minute |
|
||||
| `n` | Specific value | `hour: "14"` - at 2 PM |
|
||||
| `*/n` | Every n intervals | `minute: "*/15"` - every 15 minutes |
|
||||
| `n,m` | Multiple values | `hour: "9,12,17"` - at 9 AM, noon, and 5 PM |
|
||||
| `n-m` | Range (inclusive) | `hour: "9-17"` - 9 AM to 5 PM |
|
||||
| `n-m/s` | Range with step | `minute: "10-50/10"` - at 10, 20, 30, 40, 50 |
|
||||
| `n-m,x-y` | Multiple ranges | `hour: "0-6,20-23"` - night hours |
|
||||
|
||||
### Pattern Examples
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
# Every minute
|
||||
- name: "Heartbeat"
|
||||
minute: "*"
|
||||
hour: "*"
|
||||
weekday: "*"
|
||||
command: "echo 'alive'"
|
||||
|
||||
# Every 5 minutes
|
||||
- name: "Frequent Check"
|
||||
minute: "*/5"
|
||||
hour: "*"
|
||||
weekday: "*"
|
||||
command: "check-service.sh"
|
||||
|
||||
# At specific minutes
|
||||
- name: "Quarter Hours"
|
||||
minute: "0,15,30,45"
|
||||
hour: "*"
|
||||
weekday: "*"
|
||||
command: "quarter-hour-task.sh"
|
||||
|
||||
# Business hours only (9 AM - 5 PM)
|
||||
- name: "Business Task"
|
||||
minute: "0"
|
||||
hour: "9-17"
|
||||
weekday: "*"
|
||||
command: "business-process.sh"
|
||||
|
||||
# Complex schedule
|
||||
- name: "Complex Task"
|
||||
minute: "5-55/10" # At 5, 15, 25, 35, 45, 55
|
||||
hour: "8-18/2" # Every 2 hours from 8 AM to 6 PM
|
||||
weekday: "*"
|
||||
command: "complex-task.sh"
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
### Weekday Field Example
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "Weekday Task"
|
||||
minute: "0"
|
||||
hour: "9"
|
||||
weekday: "1-5" # Monday to Friday
|
||||
command: "weekday-task.sh"
|
||||
```
|
||||
scron/
|
||||
├── main.go # Main application entry point
|
||||
├── config.go # Configuration parsing and validation
|
||||
├── config_test.go # Configuration tests
|
||||
├── time.go # Time matching and cron logic
|
||||
├── timer_test.go # Timer and scheduling tests
|
||||
├── error.go # Custom error types
|
||||
├── config.yml # Example configuration
|
||||
└── README.md # This file
|
||||
|
||||
## Notification Configuration
|
||||
|
||||
### Gotify Configuration
|
||||
|
||||
```yaml
|
||||
notification:
|
||||
- name: gotify-alerts
|
||||
success:
|
||||
gotify:
|
||||
url: "https://gotify.example.com/message?token=YOUR_TOKEN"
|
||||
error:
|
||||
gotify:
|
||||
url: "https://gotify.example.com/message?token=YOUR_PRIORITY_TOKEN"
|
||||
```
|
||||
|
||||
Gotify notifications include:
|
||||
- ✅ Success messages with low priority (-1)
|
||||
- ❌ Error messages with high priority (10)
|
||||
- Job name, command, and output in the message body
|
||||
|
||||
### SMTP Configuration
|
||||
|
||||
```yaml
|
||||
notification:
|
||||
- name: email-alerts
|
||||
success:
|
||||
smtp:
|
||||
host: "smtp.office365.com"
|
||||
port: 587
|
||||
username: "notifications@company.com"
|
||||
password: "secure-password"
|
||||
from: "scron@company.com"
|
||||
to: "devops@company.com"
|
||||
use_ssl: true
|
||||
error:
|
||||
smtp:
|
||||
host: "smtp.office365.com"
|
||||
port: 587
|
||||
username: "alerts@company.com"
|
||||
password: "secure-password"
|
||||
from: "scron-alerts@company.com"
|
||||
to: "oncall@company.com"
|
||||
use_ssl: true
|
||||
```
|
||||
|
||||
### Multiple Notification Channels
|
||||
|
||||
You can configure both Gotify and SMTP in the same notification configuration - both will be used:
|
||||
|
||||
```yaml
|
||||
notification:
|
||||
- name: multi-channel
|
||||
error:
|
||||
gotify:
|
||||
url: "https://gotify.example.com/message?token=TOKEN"
|
||||
smtp:
|
||||
host: "smtp.gmail.com"
|
||||
port: 587
|
||||
username: "alerts@example.com"
|
||||
password: "app-password"
|
||||
from: "scron@example.com"
|
||||
to: "team@example.com"
|
||||
use_ssl: true
|
||||
```
|
||||
|
||||
## Advanced Examples
|
||||
|
||||
### Database Maintenance
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "PostgreSQL Vacuum"
|
||||
minute: "0"
|
||||
hour: "4"
|
||||
command: "psql -U postgres -d mydb -c 'VACUUM ANALYZE;'"
|
||||
notification: database-alerts
|
||||
|
||||
- name: "MySQL Backup"
|
||||
minute: "30"
|
||||
hour: "3"
|
||||
command: |
|
||||
mysqldump --all-databases > /backup/mysql_$(date +%Y%m%d_%H%M%S).sql && \
|
||||
find /backup -name "mysql_*.sql" -mtime +7 -delete
|
||||
notification: backup-alerts
|
||||
```
|
||||
|
||||
### System Monitoring
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "Disk Space Check"
|
||||
minute: "*/30"
|
||||
hour: "*"
|
||||
command: |
|
||||
USAGE=$(df -h / | awk 'NR==2 {print $5}' | sed 's/%//')
|
||||
if [ $USAGE -gt 80 ]; then
|
||||
echo "WARNING: Disk usage is at ${USAGE}%"
|
||||
exit 1
|
||||
fi
|
||||
notification: critical-alerts
|
||||
|
||||
- name: "Memory Check"
|
||||
minute: "*/10"
|
||||
hour: "*"
|
||||
command: |
|
||||
FREE_MEM=$(free -m | awk 'NR==2 {print $4}')
|
||||
if [ $FREE_MEM -lt 500 ]; then
|
||||
echo "Low memory: ${FREE_MEM}MB free"
|
||||
exit 1
|
||||
fi
|
||||
notification: system-alerts
|
||||
```
|
||||
|
||||
### Log Rotation
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "Rotate Application Logs"
|
||||
minute: "0"
|
||||
hour: "0"
|
||||
command: |
|
||||
find /var/log/myapp -name "*.log" -size +100M -exec gzip {} \; && \
|
||||
find /var/log/myapp -name "*.gz" -mtime +30 -delete
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
SCRON uses structured logging with different levels:
|
||||
SCRON uses structured JSON logging with zerolog. Logs include:
|
||||
|
||||
- **Debug**: Detailed execution information, config parsing
|
||||
- **Info**: Job execution start/completion
|
||||
- **Warn**: Non-critical issues (empty config, etc.)
|
||||
- **Error**: Execution failures, config errors
|
||||
- **Fatal**: Critical errors that stop the application
|
||||
- Job execution start/completion
|
||||
- Command output (stdout and stderr)
|
||||
- Exit codes
|
||||
- Execution errors
|
||||
- Configuration reload events
|
||||
- Notification delivery status
|
||||
|
||||
Set log level via environment variable:
|
||||
```bash
|
||||
export ZEROLOG_LEVEL=debug # debug, info, warn, error, fatal
|
||||
Example log output:
|
||||
```json
|
||||
{"level":"info","time":"2024-01-15T10:00:00Z","message":"Executing job","job":"Database Backup","command":"pg_dump mydb > backup.sql"}
|
||||
{"level":"info","time":"2024-01-15T10:00:05Z","message":"Job completed","job":"Database Backup","exit_code":0}
|
||||
```
|
||||
|
||||
## Examples
|
||||
## Error Handling
|
||||
|
||||
### Simple Periodic Task
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "Heartbeat"
|
||||
minute: "*"
|
||||
hour: "*"
|
||||
command: "echo $(date): Service running >> /var/log/heartbeat.log"
|
||||
```
|
||||
SCRON provides comprehensive error handling:
|
||||
|
||||
### Business Hours Only
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "Business Hours Check"
|
||||
minute: "0"
|
||||
hour: "9-17"
|
||||
command: "business-hours-task.sh"
|
||||
```
|
||||
- **Configuration Errors**: Invalid YAML, missing required fields
|
||||
- **Time Pattern Errors**: Invalid cron patterns with detailed error messages
|
||||
- **Execution Errors**: Command failures, non-zero exit codes
|
||||
- **Notification Errors**: Failed delivery attempts are logged but don't stop job execution
|
||||
|
||||
### Multiple Schedules
|
||||
```yaml
|
||||
jobs:
|
||||
- name: "Frequent Check"
|
||||
minute: "*/2"
|
||||
hour: "*"
|
||||
command: "quick-check.sh"
|
||||
|
||||
- name: "Hourly Report"
|
||||
minute: "0"
|
||||
hour: "*"
|
||||
command: "generate-report.sh"
|
||||
|
||||
- name: "Daily Cleanup"
|
||||
minute: "0"
|
||||
hour: "3"
|
||||
command: "cleanup.sh"
|
||||
```
|
||||
## Best Practices
|
||||
|
||||
## Troubleshooting
|
||||
1. **Use Absolute Paths**: Always use absolute paths in commands to avoid PATH issues
|
||||
2. **Set Proper Exit Codes**: Ensure your scripts exit with non-zero codes on failure
|
||||
3. **Test Commands**: Test your commands manually before adding to configuration
|
||||
4. **Monitor Logs**: Regularly check SCRON logs for execution issues
|
||||
5. **Use Notifications Wisely**: Configure different notification channels for different severity levels
|
||||
6. **Keep Commands Simple**: For complex tasks, use shell scripts instead of inline commands
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Jobs not executing:**
|
||||
- Check config.yml syntax with `go test ./...`
|
||||
- Verify file permissions on commands
|
||||
- Check logs for parsing errors
|
||||
|
||||
**High CPU usage:**
|
||||
- Normal behavior - SCRON checks time every second for precision
|
||||
- Consider reducing job frequency if not needed
|
||||
|
||||
**Commands failing:**
|
||||
- Commands run via `bash -c`, ensure bash compatibility
|
||||
- Check command paths and permissions
|
||||
- Review captured output in logs
|
||||
|
||||
### Debug Mode
|
||||
Enable debug logging to see detailed execution information:
|
||||
```bash
|
||||
ZEROLOG_LEVEL=debug ./scron
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||
3. Make your changes following the existing code style
|
||||
4. Add tests for new functionality
|
||||
5. Run tests and linting (`go test ./... && go fmt ./... && go vet ./...`)
|
||||
6. Commit your changes (`git commit -am 'Add amazing feature'`)
|
||||
7. Push to the branch (`git push origin feature/amazing-feature`)
|
||||
8. Open a Pull Request
|
||||
|
||||
## License
|
||||
|
||||
This project is open source. Please check the repository for license details.
|
||||
|
||||
---
|
||||
*Generated with Crush*
|
||||
95
config.go
95
config.go
|
|
@ -12,14 +12,43 @@ import (
|
|||
)
|
||||
|
||||
type config struct {
|
||||
Jobs []jobconfig `yaml:"jobs"`
|
||||
Jobs []jobconfig `yaml:"jobs"`
|
||||
Notification []notificationConfig `yaml:"notification"`
|
||||
}
|
||||
|
||||
type jobconfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Hour string `yaml:"hour"`
|
||||
Minute string `yaml:"minute"`
|
||||
Command string `yaml:"command"`
|
||||
Name string `yaml:"name"`
|
||||
Hour string `yaml:"hour"`
|
||||
Minute string `yaml:"minute"`
|
||||
Weekday string `yaml:"weekday"`
|
||||
Timezone string `yaml:"timezone"`
|
||||
Command string `yaml:"command"`
|
||||
Notification string `yaml:"notification,omitempty"`
|
||||
}
|
||||
|
||||
type notificationConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Success notification `yaml:"success,omitempty"`
|
||||
Error notification `yaml:"error,omitempty"`
|
||||
}
|
||||
|
||||
type notification struct {
|
||||
Gotify gotifyConfig `yaml:"gotify,omitempty"`
|
||||
Smtp smtpConfig `yaml:"smtp,omitempty"`
|
||||
}
|
||||
|
||||
type gotifyConfig struct {
|
||||
URL string `yaml:"url"`
|
||||
}
|
||||
|
||||
type smtpConfig struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
Username string `yaml:"username,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
From string `yaml:"from"`
|
||||
To string `yaml:"to"`
|
||||
UseSSL bool `yaml:"use_ssl,omitempty"`
|
||||
}
|
||||
|
||||
func ReadFromFile(path string) (config, error) {
|
||||
|
|
@ -34,11 +63,63 @@ func ReadFromFile(path string) (config, error) {
|
|||
return config{}, fmt.Errorf(ErrWrapTemplate, ErrCantParseConfigFile, err)
|
||||
}
|
||||
|
||||
cfgFolder := "/etc/scron/conf.d/"
|
||||
|
||||
if _, err := os.Stat(cfgFolder); os.IsNotExist(err) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir("./")
|
||||
if err != nil {
|
||||
return c, fmt.Errorf("cant scan confd folder: %w", err)
|
||||
}
|
||||
|
||||
for _, file := range entries {
|
||||
if !strings.HasSuffix(file.Name(), ".yml") {
|
||||
continue
|
||||
}
|
||||
|
||||
fileContent, err := os.ReadFile(cfgFolder + file.Name())
|
||||
if err != nil {
|
||||
return config{}, fmt.Errorf("cant read config file %s: %w", file.Name(), err)
|
||||
}
|
||||
|
||||
fileConfig := config{}
|
||||
|
||||
err = yaml.Unmarshal(fileContent, &fileConfig)
|
||||
if err != nil {
|
||||
return config{}, fmt.Errorf("cant unmarshal yml config: %w", err)
|
||||
}
|
||||
|
||||
c.Jobs = append(c.Jobs, fileConfig.Jobs...)
|
||||
c.Notification = append(c.Notification, fileConfig.Notification...)
|
||||
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (jc jobconfig) MatchCurrentTime(t time.Time) (bool, error) {
|
||||
matchHour, err := matchTime(jc.Hour, t.Hour())
|
||||
loc, err := time.LoadLocation("UTC")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if jc.Timezone != "" {
|
||||
loc, err = time.LoadLocation(jc.Timezone)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
matchWeekDay, err := matchTime(jc.Weekday, int(t.In(loc).Weekday()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !matchWeekDay {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
matchHour, err := matchTime(jc.Hour, t.In(loc).Hour())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -46,7 +127,7 @@ func (jc jobconfig) MatchCurrentTime(t time.Time) (bool, error) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
matchMinute, err := matchTime(jc.Minute, t.Minute())
|
||||
matchMinute, err := matchTime(jc.Minute, t.In(loc).Minute())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
|
|||
7
config.yml.example
Normal file
7
config.yml.example
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
jobs:
|
||||
- name: "Test"
|
||||
minute: "*"
|
||||
hour: "*"
|
||||
weekday: "*"
|
||||
timezone: "Europe/Berlin" # optional, default UTC
|
||||
command: "echo 'Juhu'"
|
||||
|
|
@ -146,28 +146,34 @@ func TestMatchJobTime(t *testing.T) {
|
|||
}{
|
||||
{
|
||||
name: "failed-houre",
|
||||
job: jobconfig{Hour: "15", Minute: "*"},
|
||||
job: jobconfig{Hour: "15", Minute: "*", Weekday: "*"},
|
||||
expRes: false,
|
||||
expErrMsg: "",
|
||||
},
|
||||
{
|
||||
name: "failed-minute",
|
||||
job: jobconfig{Hour: "*", Minute: "46"},
|
||||
job: jobconfig{Hour: "*", Minute: "46", Weekday: "*"},
|
||||
expRes: false,
|
||||
expErrMsg: "",
|
||||
},
|
||||
{
|
||||
name: "pass",
|
||||
job: jobconfig{Hour: "*", Minute: "*"},
|
||||
job: jobconfig{Hour: "*", Minute: "*", Weekday: "*"},
|
||||
expRes: true,
|
||||
expErrMsg: "",
|
||||
},
|
||||
{
|
||||
name: "failed-with-error",
|
||||
job: jobconfig{Hour: "abs", Minute: "*"},
|
||||
job: jobconfig{Hour: "abs", Minute: "*", Weekday: "*"},
|
||||
expRes: false,
|
||||
expErrMsg: "cant parse time pattern",
|
||||
},
|
||||
{
|
||||
name: "pass-with-timezone",
|
||||
job: jobconfig{Hour: "17", Minute: "*", Weekday: "*", Timezone: "Africa/Nairobi"},
|
||||
expRes: true,
|
||||
expErrMsg: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tts {
|
||||
|
|
|
|||
3
go.mod
3
go.mod
|
|
@ -5,6 +5,8 @@ go 1.24.1
|
|||
require (
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/urfave/cli/v3 v3.4.1
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
|
|
@ -14,5 +16,6 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -15,12 +15,18 @@ github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
|||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
|
||||
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
|
|
|||
40
gotify.go
Normal file
40
gotify.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Message string `json:"message"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
Extras map[string]interface{} `json:"extras,omitempty"`
|
||||
}
|
||||
|
||||
func SendGotifyNotification(url string, msg Message) error {
|
||||
bodyBytes, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode payload: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(bodyBytes))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HTTP request error: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status: %s", resp.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
15
jobResult.go
Normal file
15
jobResult.go
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package main
|
||||
|
||||
type jobResult struct {
|
||||
job jobconfig
|
||||
outputString string
|
||||
execCode int
|
||||
err error
|
||||
}
|
||||
|
||||
func (j jobResult) isSuccess() bool {
|
||||
if j.execCode == 0 && j.err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
140
main.go
140
main.go
|
|
@ -2,17 +2,62 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
var currentConfig config
|
||||
|
||||
func main() {
|
||||
// zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
||||
|
||||
log.Info().Msg("Start")
|
||||
|
||||
cmd := &cli.Command{
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "run",
|
||||
Usage: "run the scron",
|
||||
Action: func(context.Context, *cli.Command) error {
|
||||
deamon()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dump-config",
|
||||
Usage: "show the complete config",
|
||||
Action: func(context.Context, *cli.Command) error {
|
||||
var err error
|
||||
currentConfig, err = ReadFromFile("config.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := yaml.Marshal(currentConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(res))
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := cmd.Run(context.Background(), os.Args); err != nil {
|
||||
log.Fatal().Err(err).Msg("error calling scron")
|
||||
}
|
||||
}
|
||||
|
||||
func deamon() {
|
||||
var err error
|
||||
currentConfig, err = ReadFromFile("config.yml")
|
||||
if err != nil {
|
||||
|
|
@ -29,16 +74,19 @@ func main() {
|
|||
for {
|
||||
select {
|
||||
case t := <-cronTicker:
|
||||
fmt.Println("Tick bei Sekunde 0:", t.Format(time.RFC3339Nano))
|
||||
execucteJobs(t, currentConfig.Jobs)
|
||||
case t := <-configTicker:
|
||||
fmt.Println("Tick bei Sekunde 50:", t.Format(time.RFC3339Nano))
|
||||
execucteJobs(t, currentConfig)
|
||||
case <-configTicker:
|
||||
log.Debug().Msg("Reload Config")
|
||||
currentConfig, err = ReadFromFile("config.yml")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cant read config")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func execucteJobs(t time.Time, jobs []jobconfig) {
|
||||
for _, job := range jobs {
|
||||
func execucteJobs(t time.Time, c config) {
|
||||
for _, job := range c.Jobs {
|
||||
execute, err := job.MatchCurrentTime(t)
|
||||
log.Debug().Bool("execution", execute).Time("t", t).Msg("check cron execution")
|
||||
if err != nil {
|
||||
|
|
@ -48,34 +96,90 @@ func execucteJobs(t time.Time, jobs []jobconfig) {
|
|||
continue
|
||||
}
|
||||
|
||||
_ = executeCommand(job)
|
||||
go executeJob(t, job, c)
|
||||
}
|
||||
}
|
||||
|
||||
func executeCommand(job jobconfig) error {
|
||||
l := log.With().Str("name", job.Name).Str("command", job.Command).Logger()
|
||||
func executeJob(t time.Time, job jobconfig, c config) {
|
||||
result, err := executeCommand(job)
|
||||
log.Info().Str("name", job.Name).Str("command", job.Command).Time("execute-for", t).Int("execCode", result.execCode).Str("output", result.outputString).Err(result.err).Msg("Done execute task")
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Error while Execute command")
|
||||
}
|
||||
for _, n := range c.Notification {
|
||||
if n.Name == job.Notification {
|
||||
sendNotification(result, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sendNotification(result jobResult, notification notificationConfig) error {
|
||||
title := ""
|
||||
msg := Message{}
|
||||
msg.Priority = 10
|
||||
|
||||
if result.isSuccess() {
|
||||
title = "✅ [scron][success] " + result.job.Name
|
||||
msg.Priority = -1
|
||||
} else {
|
||||
title = "❌ [scron][error] " + result.job.Name
|
||||
}
|
||||
|
||||
msg.Message = result.outputString
|
||||
msg.Title = title
|
||||
|
||||
if result.isSuccess() {
|
||||
if notification.Success.Gotify.URL != "" {
|
||||
err := SendGotifyNotification(notification.Success.Gotify.URL, msg)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cant send gotify notification")
|
||||
}
|
||||
}
|
||||
if notification.Success.Smtp.Host != "" {
|
||||
err := SendSMTPNotification(notification.Success.Smtp, title, result.outputString)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cant send smtp notification")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if notification.Error.Gotify.URL != "" {
|
||||
err := SendGotifyNotification(notification.Error.Gotify.URL, msg)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cant send gotify notification")
|
||||
}
|
||||
}
|
||||
if notification.Error.Smtp.Host != "" {
|
||||
err := SendSMTPNotification(notification.Error.Smtp, title, result.outputString)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("cant send smtp notification")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func executeCommand(job jobconfig) (jobResult, error) {
|
||||
j := jobResult{}
|
||||
j.job = job
|
||||
var output bytes.Buffer
|
||||
cmd := exec.Command("bash", "-c", job.Command)
|
||||
cmd.Stdout = &output
|
||||
cmd.Stderr = &output
|
||||
l.Debug().Msg("Start Execution")
|
||||
err := cmd.Run()
|
||||
|
||||
outString := output.String()
|
||||
j.outputString = output.String()
|
||||
|
||||
exitCode := 0
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||
exitCode = status.ExitStatus()
|
||||
j.execCode = status.ExitStatus()
|
||||
}
|
||||
} else {
|
||||
l.Error().Err(err).Str("output", outString).Msg("Faild Execution")
|
||||
return err
|
||||
j.err = err
|
||||
return j, err
|
||||
}
|
||||
}
|
||||
|
||||
l.Debug().Err(err).Str("output", outString).Int("exitcode", exitCode).Msg("Success Execution")
|
||||
|
||||
return nil
|
||||
return j, nil
|
||||
}
|
||||
|
|
|
|||
13
scripts/postinstall.sh
Executable file
13
scripts/postinstall.sh
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Reload systemd and enable service
|
||||
systemctl daemon-reload
|
||||
systemctl enable scron
|
||||
systemctl start scron
|
||||
|
||||
echo "SCRON installed successfully!"
|
||||
echo "Configuration file: /etc/scron/config.yml"
|
||||
echo "Start service: systemctl start scron"
|
||||
echo "View logs: journalctl -u scron -f"
|
||||
|
||||
8
scripts/postremove.sh
Executable file
8
scripts/postremove.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Remove systemd service file
|
||||
systemctl daemon-reload
|
||||
|
||||
# Optionally remove user (commented out to preserve logs/data)
|
||||
# userdel scron 2>/dev/null || true
|
||||
10
scripts/preremove.sh
Executable file
10
scripts/preremove.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Stop and disable service before removal
|
||||
if systemctl is-active --quiet scron; then
|
||||
systemctl stop scron
|
||||
fi
|
||||
if systemctl is-enabled --quiet scron; then
|
||||
systemctl disable scron
|
||||
fi
|
||||
18
scripts/scron.service
Normal file
18
scripts/scron.service
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[Unit]
|
||||
Description=SCRON - Lightweight Cron Scheduler
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Group=root
|
||||
WorkingDirectory=/etc/scron
|
||||
ExecStart=/usr/bin/scron run
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=scron
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
38
smtp.go
Normal file
38
smtp.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"gopkg.in/mail.v2"
|
||||
)
|
||||
|
||||
func SendSMTPNotification(sc smtpConfig, title, message string) error {
|
||||
// Validate required fields
|
||||
if sc.Host == "" || sc.Port == 0 || sc.From == "" || sc.To == "" {
|
||||
return fmt.Errorf("incomplete SMTP configuration")
|
||||
}
|
||||
|
||||
// Create new message
|
||||
m := mail.NewMessage()
|
||||
m.SetHeader("From", sc.From)
|
||||
|
||||
// Split and trim recipients
|
||||
m.SetHeader("To", sc.To)
|
||||
m.SetHeader("Subject", title)
|
||||
m.SetBody("text/plain", message)
|
||||
|
||||
// Create SMTP dialer
|
||||
d := mail.NewDialer(sc.Host, sc.Port, sc.Username, sc.Password)
|
||||
|
||||
// Configure TLS
|
||||
d.SSL = sc.UseSSL
|
||||
|
||||
// Send the email
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
return fmt.Errorf("failed to send email: %w", err)
|
||||
}
|
||||
|
||||
log.Info().Str("subject", title).Str("to", sc.To).Msg("Email notification sent")
|
||||
return nil
|
||||
}
|
||||
|
|
@ -35,7 +35,6 @@ func (f *fakeTimeProvider) NewTicker(d time.Duration) Ticker {
|
|||
}
|
||||
|
||||
func TestCreateSecondTickers(t *testing.T) {
|
||||
t.Skip("to long while dev")
|
||||
// Vorbereitung
|
||||
fakeNow := time.Date(2025, 7, 30, 12, 34, 45, 0, time.UTC)
|
||||
ftp := &fakeTimeProvider{currentTime: fakeNow}
|
||||
|
|
@ -43,7 +42,7 @@ func TestCreateSecondTickers(t *testing.T) {
|
|||
// Funktion aufrufen
|
||||
ch0, ch50 := createTickers(ftp)
|
||||
|
||||
time.Sleep(15 * time.Second)
|
||||
time.Sleep(20 * time.Second)
|
||||
// Channels auslösen
|
||||
if len(ftp.tickers) != 2 {
|
||||
t.Fatalf("expected 2 tickers, got %d", len(ftp.tickers))
|
||||
|
|
|
|||
11
vendor/github.com/urfave/cli/v3/.gitignore
generated
vendored
Normal file
11
vendor/github.com/urfave/cli/v3/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
*.coverprofile
|
||||
*.exe
|
||||
*.orig
|
||||
.*envrc
|
||||
.envrc
|
||||
.idea
|
||||
/.local/
|
||||
/site/
|
||||
coverage.txt
|
||||
examples/*/built-example
|
||||
vendor
|
||||
13
vendor/github.com/urfave/cli/v3/.golangci.yaml
generated
vendored
Normal file
13
vendor/github.com/urfave/cli/v3/.golangci.yaml
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
version: "2"
|
||||
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- makezero
|
||||
- misspell
|
||||
exclusions:
|
||||
presets:
|
||||
- std-error-handling
|
||||
75
vendor/github.com/urfave/cli/v3/CODE_OF_CONDUCT.md
generated
vendored
Normal file
75
vendor/github.com/urfave/cli/v3/CODE_OF_CONDUCT.md
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
education, socio-economic status, nationality, personal appearance, race,
|
||||
religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting urfave-governance@googlegroups.com, a members-only group
|
||||
that is world-postable. All complaints will be reviewed and investigated and
|
||||
will result in a response that is deemed necessary and appropriate to the
|
||||
circumstances. The project team is obligated to maintain confidentiality with
|
||||
regard to the reporter of an incident. Further details of specific enforcement
|
||||
policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
21
vendor/github.com/urfave/cli/v3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/urfave/cli/v3/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 urfave/cli maintainers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
26
vendor/github.com/urfave/cli/v3/Makefile
generated
vendored
Normal file
26
vendor/github.com/urfave/cli/v3/Makefile
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# NOTE: this Makefile is meant to provide a simplified entry point for humans to
|
||||
# run all of the critical steps to verify one's changes are harmonious in
|
||||
# nature. Keeping target bodies to one line each and abstaining from make magic
|
||||
# are very important so that maintainers and contributors can focus their
|
||||
# attention on files that are primarily Go.
|
||||
|
||||
GO_RUN_BUILD := go run scripts/build.go
|
||||
|
||||
.PHONY: all
|
||||
all: generate vet test check-binary-size gfmrun
|
||||
|
||||
# NOTE: this is a special catch-all rule to run any of the commands
|
||||
# defined in scripts/build.go with optional arguments passed
|
||||
# via GFLAGS (global flags) and FLAGS (command-specific flags), e.g.:
|
||||
#
|
||||
# $ make test GFLAGS='--packages cli'
|
||||
%:
|
||||
$(GO_RUN_BUILD) $(GFLAGS) $* $(FLAGS)
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
mkdocs build
|
||||
|
||||
.PHONY: serve-docs
|
||||
serve-docs:
|
||||
mkdocs serve
|
||||
56
vendor/github.com/urfave/cli/v3/README.md
generated
vendored
Normal file
56
vendor/github.com/urfave/cli/v3/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Welcome to urfave/cli
|
||||
|
||||
[![Go Reference][goreference_badge]][goreference_link]
|
||||
[![Go Report Card][goreportcard_badge]][goreportcard_link]
|
||||
[![codecov][codecov_badge]][codecov_link]
|
||||
[![Tests status][test_badge]][test_link]
|
||||
|
||||
urfave/cli is a **declarative**, simple, fast, and fun package for building
|
||||
command line tools in Go featuring:
|
||||
|
||||
- commands and subcommands with alias and prefix match support
|
||||
- flexible and permissive help system
|
||||
- dynamic shell completion for `bash`, `zsh`, `fish`, and `powershell`
|
||||
- no dependencies except Go standard library
|
||||
- input flags for simple types, slices of simple types, time, duration, and
|
||||
others
|
||||
- compound short flag support (`-a` `-b` `-c` can be shortened to `-abc`)
|
||||
- documentation generation in `man` and Markdown (supported via the
|
||||
[`urfave/cli-docs`][urfave/cli-docs] module)
|
||||
- input lookup from:
|
||||
- environment variables
|
||||
- plain text files
|
||||
- structured file formats (supported via the
|
||||
[`urfave/cli-altsrc`][urfave/cli-altsrc] module)
|
||||
|
||||
## Documentation
|
||||
|
||||
See the hosted documentation website at <https://cli.urfave.org>. Contents of
|
||||
this website are built from the [`./docs`](./docs) directory.
|
||||
|
||||
## Support
|
||||
|
||||
Check the [Q&A discussions]. If you don't find answer to your question, [create
|
||||
a new discussion].
|
||||
|
||||
If you found a bug or have a feature request, [create a new issue].
|
||||
|
||||
Please keep in mind that this project is run by unpaid volunteers.
|
||||
|
||||
### License
|
||||
|
||||
See [`LICENSE`](./LICENSE).
|
||||
|
||||
[test_badge]: https://github.com/urfave/cli/actions/workflows/test.yml/badge.svg
|
||||
[test_link]: https://github.com/urfave/cli/actions/workflows/test.yml
|
||||
[goreference_badge]: https://pkg.go.dev/badge/github.com/urfave/cli/v3.svg
|
||||
[goreference_link]: https://pkg.go.dev/github.com/urfave/cli/v3
|
||||
[goreportcard_badge]: https://goreportcard.com/badge/github.com/urfave/cli/v3
|
||||
[goreportcard_link]: https://goreportcard.com/report/github.com/urfave/cli/v3
|
||||
[codecov_badge]: https://codecov.io/gh/urfave/cli/branch/main/graph/badge.svg?token=t9YGWLh05g
|
||||
[codecov_link]: https://codecov.io/gh/urfave/cli
|
||||
[Q&A discussions]: https://github.com/urfave/cli/discussions/categories/q-a
|
||||
[create a new discussion]: https://github.com/urfave/cli/discussions/new?category=q-a
|
||||
[urfave/cli-docs]: https://github.com/urfave/cli-docs
|
||||
[urfave/cli-altsrc]: https://github.com/urfave/cli-altsrc
|
||||
[create a new issue]: https://github.com/urfave/cli/issues/new/choose
|
||||
402
vendor/github.com/urfave/cli/v3/args.go
generated
vendored
Normal file
402
vendor/github.com/urfave/cli/v3/args.go
generated
vendored
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Args interface {
|
||||
// Get returns the nth argument, or else a blank string
|
||||
Get(n int) string
|
||||
// First returns the first argument, or else a blank string
|
||||
First() string
|
||||
// Tail returns the rest of the arguments (not the first one)
|
||||
// or else an empty string slice
|
||||
Tail() []string
|
||||
// Len returns the length of the wrapped slice
|
||||
Len() int
|
||||
// Present checks if there are any arguments present
|
||||
Present() bool
|
||||
// Slice returns a copy of the internal slice
|
||||
Slice() []string
|
||||
}
|
||||
|
||||
type stringSliceArgs struct {
|
||||
v []string
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Get(n int) string {
|
||||
if len(a.v) > n {
|
||||
return a.v[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) First() string {
|
||||
return a.Get(0)
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Tail() []string {
|
||||
if a.Len() >= 2 {
|
||||
tail := a.v[1:]
|
||||
ret := make([]string, len(tail))
|
||||
copy(ret, tail)
|
||||
return ret
|
||||
}
|
||||
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Len() int {
|
||||
return len(a.v)
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Present() bool {
|
||||
return a.Len() != 0
|
||||
}
|
||||
|
||||
func (a *stringSliceArgs) Slice() []string {
|
||||
ret := make([]string, len(a.v))
|
||||
copy(ret, a.v)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Argument captures a positional argument that can
|
||||
// be parsed
|
||||
type Argument interface {
|
||||
// which this argument can be accessed using the given name
|
||||
HasName(string) bool
|
||||
|
||||
// Parse the given args and return unparsed args and/or error
|
||||
Parse([]string) ([]string, error)
|
||||
|
||||
// The usage template for this argument to use in help
|
||||
Usage() string
|
||||
|
||||
// The Value of this Arg
|
||||
Get() any
|
||||
}
|
||||
|
||||
// AnyArguments to differentiate between no arguments(nil) vs aleast one
|
||||
var AnyArguments = []Argument{
|
||||
&StringArgs{
|
||||
Max: -1,
|
||||
},
|
||||
}
|
||||
|
||||
type ArgumentBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
Name string `json:"name"` // the name of this argument
|
||||
Value T `json:"value"` // the default value of this argument
|
||||
Destination *T `json:"-"` // the destination point for this argument
|
||||
UsageText string `json:"usageText"` // the usage text to show
|
||||
Config C `json:"config"` // config for this argument similar to Flag Config
|
||||
|
||||
value *T
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) HasName(s string) bool {
|
||||
return s == a.Name
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) Usage() string {
|
||||
if a.UsageText != "" {
|
||||
return a.UsageText
|
||||
}
|
||||
|
||||
usageFormat := "%[1]s"
|
||||
return fmt.Sprintf(usageFormat, a.Name)
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) Parse(s []string) ([]string, error) {
|
||||
tracef("calling arg%[1] parse with args %[2]", a.Name, s)
|
||||
|
||||
var vc VC
|
||||
var t T
|
||||
value := vc.Create(a.Value, &t, a.Config)
|
||||
a.value = &t
|
||||
|
||||
tracef("attempting arg%[1] parse", &a.Name)
|
||||
if len(s) > 0 {
|
||||
if err := value.Set(s[0]); err != nil {
|
||||
return s, err
|
||||
}
|
||||
*a.value = value.Get().(T)
|
||||
tracef("set arg%[1] one value", a.Name, *a.value)
|
||||
}
|
||||
|
||||
if a.Destination != nil {
|
||||
tracef("setting destination")
|
||||
*a.Destination = *a.value
|
||||
}
|
||||
|
||||
if len(s) > 0 {
|
||||
return s[1:], nil
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (a *ArgumentBase[T, C, VC]) Get() any {
|
||||
if a.value != nil {
|
||||
return *a.value
|
||||
}
|
||||
return a.Value
|
||||
}
|
||||
|
||||
// ArgumentsBase is a base type for slice arguments
|
||||
type ArgumentsBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
Name string `json:"name"` // the name of this argument
|
||||
Value T `json:"value"` // the default value of this argument
|
||||
Destination *[]T `json:"-"` // the destination point for this argument
|
||||
UsageText string `json:"usageText"` // the usage text to show
|
||||
Min int `json:"minTimes"` // the min num of occurrences of this argument
|
||||
Max int `json:"maxTimes"` // the max num of occurrences of this argument, set to -1 for unlimited
|
||||
Config C `json:"config"` // config for this argument similar to Flag Config
|
||||
|
||||
values []T
|
||||
}
|
||||
|
||||
func (a *ArgumentsBase[T, C, VC]) HasName(s string) bool {
|
||||
return s == a.Name
|
||||
}
|
||||
|
||||
func (a *ArgumentsBase[T, C, VC]) Usage() string {
|
||||
if a.UsageText != "" {
|
||||
return a.UsageText
|
||||
}
|
||||
|
||||
usageFormat := ""
|
||||
if a.Min == 0 {
|
||||
if a.Max == 1 {
|
||||
usageFormat = "[%[1]s]"
|
||||
} else {
|
||||
usageFormat = "[%[1]s ...]"
|
||||
}
|
||||
} else {
|
||||
usageFormat = "%[1]s [%[1]s ...]"
|
||||
}
|
||||
return fmt.Sprintf(usageFormat, a.Name)
|
||||
}
|
||||
|
||||
func (a *ArgumentsBase[T, C, VC]) Parse(s []string) ([]string, error) {
|
||||
tracef("calling arg%[1] parse with args %[2]", &a.Name, s)
|
||||
if a.Max == 0 {
|
||||
fmt.Printf("WARNING args %s has max 0, not parsing argument\n", a.Name)
|
||||
return s, nil
|
||||
}
|
||||
if a.Max != -1 && a.Min > a.Max {
|
||||
fmt.Printf("WARNING args %s has min[%d] > max[%d], not parsing argument\n", a.Name, a.Min, a.Max)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
count := 0
|
||||
var vc VC
|
||||
var t T
|
||||
value := vc.Create(a.Value, &t, a.Config)
|
||||
a.values = []T{}
|
||||
|
||||
tracef("attempting arg%[1] parse", &a.Name)
|
||||
for _, arg := range s {
|
||||
if err := value.Set(arg); err != nil {
|
||||
return s, err
|
||||
}
|
||||
tracef("set arg%[1] one value", &a.Name, value.Get().(T))
|
||||
a.values = append(a.values, value.Get().(T))
|
||||
count++
|
||||
if count >= a.Max && a.Max > -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if count < a.Min {
|
||||
return s, fmt.Errorf("sufficient count of arg %s not provided, given %d expected %d", a.Name, count, a.Min)
|
||||
}
|
||||
|
||||
if a.Destination != nil {
|
||||
tracef("appending destination")
|
||||
*a.Destination = a.values // append(*a.Destination, a.values...)
|
||||
}
|
||||
|
||||
return s[count:], nil
|
||||
}
|
||||
|
||||
func (a *ArgumentsBase[T, C, VC]) Get() any {
|
||||
if a.values != nil {
|
||||
return a.values
|
||||
}
|
||||
return []T{}
|
||||
}
|
||||
|
||||
type (
|
||||
FloatArg = ArgumentBase[float64, NoConfig, floatValue[float64]]
|
||||
Float32Arg = ArgumentBase[float32, NoConfig, floatValue[float32]]
|
||||
Float64Arg = ArgumentBase[float64, NoConfig, floatValue[float64]]
|
||||
IntArg = ArgumentBase[int, IntegerConfig, intValue[int]]
|
||||
Int8Arg = ArgumentBase[int8, IntegerConfig, intValue[int8]]
|
||||
Int16Arg = ArgumentBase[int16, IntegerConfig, intValue[int16]]
|
||||
Int32Arg = ArgumentBase[int32, IntegerConfig, intValue[int32]]
|
||||
Int64Arg = ArgumentBase[int64, IntegerConfig, intValue[int64]]
|
||||
StringArg = ArgumentBase[string, StringConfig, stringValue]
|
||||
StringMapArgs = ArgumentBase[map[string]string, StringConfig, StringMap]
|
||||
TimestampArg = ArgumentBase[time.Time, TimestampConfig, timestampValue]
|
||||
UintArg = ArgumentBase[uint, IntegerConfig, uintValue[uint]]
|
||||
Uint8Arg = ArgumentBase[uint8, IntegerConfig, uintValue[uint8]]
|
||||
Uint16Arg = ArgumentBase[uint16, IntegerConfig, uintValue[uint16]]
|
||||
Uint32Arg = ArgumentBase[uint32, IntegerConfig, uintValue[uint32]]
|
||||
Uint64Arg = ArgumentBase[uint64, IntegerConfig, uintValue[uint64]]
|
||||
|
||||
FloatArgs = ArgumentsBase[float64, NoConfig, floatValue[float64]]
|
||||
Float32Args = ArgumentsBase[float32, NoConfig, floatValue[float32]]
|
||||
Float64Args = ArgumentsBase[float64, NoConfig, floatValue[float64]]
|
||||
IntArgs = ArgumentsBase[int, IntegerConfig, intValue[int]]
|
||||
Int8Args = ArgumentsBase[int8, IntegerConfig, intValue[int8]]
|
||||
Int16Args = ArgumentsBase[int16, IntegerConfig, intValue[int16]]
|
||||
Int32Args = ArgumentsBase[int32, IntegerConfig, intValue[int32]]
|
||||
Int64Args = ArgumentsBase[int64, IntegerConfig, intValue[int64]]
|
||||
StringArgs = ArgumentsBase[string, StringConfig, stringValue]
|
||||
TimestampArgs = ArgumentsBase[time.Time, TimestampConfig, timestampValue]
|
||||
UintArgs = ArgumentsBase[uint, IntegerConfig, uintValue[uint]]
|
||||
Uint8Args = ArgumentsBase[uint8, IntegerConfig, uintValue[uint8]]
|
||||
Uint16Args = ArgumentsBase[uint16, IntegerConfig, uintValue[uint16]]
|
||||
Uint32Args = ArgumentsBase[uint32, IntegerConfig, uintValue[uint32]]
|
||||
Uint64Args = ArgumentsBase[uint64, IntegerConfig, uintValue[uint64]]
|
||||
)
|
||||
|
||||
func (c *Command) getArgValue(name string) any {
|
||||
tracef("command %s looking for args %s", c.Name, name)
|
||||
for _, arg := range c.Arguments {
|
||||
if arg.HasName(name) {
|
||||
tracef("command %s found args %s", c.Name, name)
|
||||
return arg.Get()
|
||||
}
|
||||
}
|
||||
tracef("command %s did not find args %s", c.Name, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func arg[T any](name string, c *Command) T {
|
||||
val := c.getArgValue(name)
|
||||
if a, ok := val.(T); ok {
|
||||
return a
|
||||
}
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
func (c *Command) StringArg(name string) string {
|
||||
return arg[string](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) StringArgs(name string) []string {
|
||||
return arg[[]string](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) FloatArg(name string) float64 {
|
||||
return arg[float64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) FloatArgs(name string) []float64 {
|
||||
return arg[[]float64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Float32Arg(name string) float32 {
|
||||
return arg[float32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Float32Args(name string) []float32 {
|
||||
return arg[[]float32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Float64Arg(name string) float64 {
|
||||
return arg[float64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Float64Args(name string) []float64 {
|
||||
return arg[[]float64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) IntArg(name string) int {
|
||||
return arg[int](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) IntArgs(name string) []int {
|
||||
return arg[[]int](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int8Arg(name string) int8 {
|
||||
return arg[int8](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int8Args(name string) []int8 {
|
||||
return arg[[]int8](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int16Arg(name string) int16 {
|
||||
return arg[int16](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int16Args(name string) []int16 {
|
||||
return arg[[]int16](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int32Arg(name string) int32 {
|
||||
return arg[int32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int32Args(name string) []int32 {
|
||||
return arg[[]int32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int64Arg(name string) int64 {
|
||||
return arg[int64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Int64Args(name string) []int64 {
|
||||
return arg[[]int64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) UintArg(name string) uint {
|
||||
return arg[uint](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint8Arg(name string) uint8 {
|
||||
return arg[uint8](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint16Arg(name string) uint16 {
|
||||
return arg[uint16](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint32Arg(name string) uint32 {
|
||||
return arg[uint32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint64Arg(name string) uint64 {
|
||||
return arg[uint64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) UintArgs(name string) []uint {
|
||||
return arg[[]uint](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint8Args(name string) []uint8 {
|
||||
return arg[[]uint8](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint16Args(name string) []uint16 {
|
||||
return arg[[]uint16](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint32Args(name string) []uint32 {
|
||||
return arg[[]uint32](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) Uint64Args(name string) []uint64 {
|
||||
return arg[[]uint64](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) TimestampArg(name string) time.Time {
|
||||
return arg[time.Time](name, c)
|
||||
}
|
||||
|
||||
func (c *Command) TimestampArgs(name string) []time.Time {
|
||||
return arg[[]time.Time](name, c)
|
||||
}
|
||||
34
vendor/github.com/urfave/cli/v3/autocomplete/bash_autocomplete
generated
vendored
Normal file
34
vendor/github.com/urfave/cli/v3/autocomplete/bash_autocomplete
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This is a shell completion script auto-generated by https://github.com/urfave/cli for bash.
|
||||
|
||||
# Macs have bash3 for which the bash-completion package doesn't include
|
||||
# _init_completion. This is a minimal version of that function.
|
||||
__%[1]s_init_completion() {
|
||||
COMPREPLY=()
|
||||
_get_comp_words_by_ref "$@" cur prev words cword
|
||||
}
|
||||
|
||||
__%[1]s_bash_autocomplete() {
|
||||
if [[ "${COMP_WORDS[0]}" != "source" ]]; then
|
||||
local cur opts base words
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
if declare -F _init_completion >/dev/null 2>&1; then
|
||||
_init_completion -n "=:" || return
|
||||
else
|
||||
__%[1]s_init_completion -n "=:" || return
|
||||
fi
|
||||
words=("${words[@]:0:$cword}")
|
||||
if [[ "$cur" == "-"* ]]; then
|
||||
requestComp="${words[*]} ${cur} --generate-shell-completion"
|
||||
else
|
||||
requestComp="${words[*]} --generate-shell-completion"
|
||||
fi
|
||||
opts=$(eval "${requestComp}" 2>/dev/null)
|
||||
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o bashdefault -o default -o nospace -F __%[1]s_bash_autocomplete %[1]s
|
||||
9
vendor/github.com/urfave/cli/v3/autocomplete/powershell_autocomplete.ps1
generated
vendored
Normal file
9
vendor/github.com/urfave/cli/v3/autocomplete/powershell_autocomplete.ps1
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
$fn = $($MyInvocation.MyCommand.Name)
|
||||
$name = $fn -replace "(.*)\.ps1$", '$1'
|
||||
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
|
||||
param($commandName, $wordToComplete, $cursorPosition)
|
||||
$other = "$wordToComplete --generate-shell-completion"
|
||||
Invoke-Expression $other | ForEach-Object {
|
||||
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
||||
}
|
||||
}
|
||||
29
vendor/github.com/urfave/cli/v3/autocomplete/zsh_autocomplete
generated
vendored
Normal file
29
vendor/github.com/urfave/cli/v3/autocomplete/zsh_autocomplete
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#compdef %[1]s
|
||||
compdef _%[1]s %[1]s
|
||||
|
||||
# This is a shell completion script auto-generated by https://github.com/urfave/cli for zsh.
|
||||
|
||||
_%[1]s() {
|
||||
local -a opts # Declare a local array
|
||||
local current
|
||||
current=${words[-1]} # -1 means "the last element"
|
||||
if [[ "$current" == "-"* ]]; then
|
||||
# Current word starts with a hyphen, so complete flags/options
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} ${current} --generate-shell-completion)}")
|
||||
else
|
||||
# Current word does not start with a hyphen, so complete subcommands
|
||||
opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
|
||||
fi
|
||||
|
||||
if [[ "${opts[1]}" != "" ]]; then
|
||||
_describe 'values' opts
|
||||
else
|
||||
_files
|
||||
fi
|
||||
}
|
||||
|
||||
# Don't run the completion function when being source-ed or eval-ed.
|
||||
# See https://github.com/urfave/cli/issues/1874 for discussion.
|
||||
if [ "$funcstack[1]" = "_%[1]s" ]; then
|
||||
_%[1]s
|
||||
fi
|
||||
195
vendor/github.com/urfave/cli/v3/category.go
generated
vendored
Normal file
195
vendor/github.com/urfave/cli/v3/category.go
generated
vendored
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
package cli
|
||||
|
||||
import "sort"
|
||||
|
||||
// CommandCategories interface allows for category manipulation
|
||||
type CommandCategories interface {
|
||||
// AddCommand adds a command to a category, creating a new category if necessary.
|
||||
AddCommand(category string, command *Command)
|
||||
// Categories returns a slice of categories sorted by name
|
||||
Categories() []CommandCategory
|
||||
}
|
||||
|
||||
type commandCategories []*commandCategory
|
||||
|
||||
func newCommandCategories() CommandCategories {
|
||||
ret := commandCategories([]*commandCategory{})
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (c *commandCategories) Less(i, j int) bool {
|
||||
return lexicographicLess((*c)[i].Name(), (*c)[j].Name())
|
||||
}
|
||||
|
||||
func (c *commandCategories) Len() int {
|
||||
return len(*c)
|
||||
}
|
||||
|
||||
func (c *commandCategories) Swap(i, j int) {
|
||||
(*c)[i], (*c)[j] = (*c)[j], (*c)[i]
|
||||
}
|
||||
|
||||
func (c *commandCategories) AddCommand(category string, command *Command) {
|
||||
for _, commandCategory := range []*commandCategory(*c) {
|
||||
if commandCategory.name == category {
|
||||
commandCategory.commands = append(commandCategory.commands, command)
|
||||
return
|
||||
}
|
||||
}
|
||||
newVal := append(*c,
|
||||
&commandCategory{name: category, commands: []*Command{command}})
|
||||
*c = newVal
|
||||
}
|
||||
|
||||
func (c *commandCategories) Categories() []CommandCategory {
|
||||
ret := make([]CommandCategory, len(*c))
|
||||
for i, cat := range *c {
|
||||
ret[i] = cat
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// CommandCategory is a category containing commands.
|
||||
type CommandCategory interface {
|
||||
// Name returns the category name string
|
||||
Name() string
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
VisibleCommands() []*Command
|
||||
}
|
||||
|
||||
type commandCategory struct {
|
||||
name string
|
||||
commands []*Command
|
||||
}
|
||||
|
||||
func (c *commandCategory) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
func (c *commandCategory) VisibleCommands() []*Command {
|
||||
if c.commands == nil {
|
||||
c.commands = []*Command{}
|
||||
}
|
||||
|
||||
var ret []*Command
|
||||
for _, command := range c.commands {
|
||||
if !command.Hidden {
|
||||
ret = append(ret, command)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// FlagCategories interface allows for category manipulation
|
||||
type FlagCategories interface {
|
||||
// AddFlags adds a flag to a category, creating a new category if necessary.
|
||||
AddFlag(category string, fl Flag)
|
||||
// VisibleCategories returns a slice of visible flag categories sorted by name
|
||||
VisibleCategories() []VisibleFlagCategory
|
||||
}
|
||||
|
||||
type defaultFlagCategories struct {
|
||||
m map[string]*defaultVisibleFlagCategory
|
||||
}
|
||||
|
||||
func newFlagCategories() FlagCategories {
|
||||
return &defaultFlagCategories{
|
||||
m: map[string]*defaultVisibleFlagCategory{},
|
||||
}
|
||||
}
|
||||
|
||||
func newFlagCategoriesFromFlags(fs []Flag) FlagCategories {
|
||||
fc := newFlagCategories()
|
||||
|
||||
var categorized bool
|
||||
|
||||
for _, fl := range fs {
|
||||
if cf, ok := fl.(CategorizableFlag); ok {
|
||||
visible := false
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
visible = vf.IsVisible()
|
||||
}
|
||||
if cat := cf.GetCategory(); cat != "" && visible {
|
||||
fc.AddFlag(cat, fl)
|
||||
categorized = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if categorized {
|
||||
for _, fl := range fs {
|
||||
if cf, ok := fl.(CategorizableFlag); ok {
|
||||
visible := false
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
visible = vf.IsVisible()
|
||||
}
|
||||
if cf.GetCategory() == "" && visible {
|
||||
fc.AddFlag("", fl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fc
|
||||
}
|
||||
|
||||
func (f *defaultFlagCategories) AddFlag(category string, fl Flag) {
|
||||
if _, ok := f.m[category]; !ok {
|
||||
f.m[category] = &defaultVisibleFlagCategory{name: category, m: map[string]Flag{}}
|
||||
}
|
||||
|
||||
f.m[category].m[fl.String()] = fl
|
||||
}
|
||||
|
||||
func (f *defaultFlagCategories) VisibleCategories() []VisibleFlagCategory {
|
||||
catNames := []string{}
|
||||
for name := range f.m {
|
||||
catNames = append(catNames, name)
|
||||
}
|
||||
|
||||
sort.Strings(catNames)
|
||||
|
||||
ret := make([]VisibleFlagCategory, len(catNames))
|
||||
for i, name := range catNames {
|
||||
ret[i] = f.m[name]
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlagCategory is a category containing flags.
|
||||
type VisibleFlagCategory interface {
|
||||
// Name returns the category name string
|
||||
Name() string
|
||||
// Flags returns a slice of VisibleFlag sorted by name
|
||||
Flags() []Flag
|
||||
}
|
||||
|
||||
type defaultVisibleFlagCategory struct {
|
||||
name string
|
||||
m map[string]Flag
|
||||
}
|
||||
|
||||
func (fc *defaultVisibleFlagCategory) Name() string {
|
||||
return fc.name
|
||||
}
|
||||
|
||||
func (fc *defaultVisibleFlagCategory) Flags() []Flag {
|
||||
vfNames := []string{}
|
||||
for flName, fl := range fc.m {
|
||||
if vf, ok := fl.(VisibleFlag); ok {
|
||||
if vf.IsVisible() {
|
||||
vfNames = append(vfNames, flName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(vfNames)
|
||||
|
||||
ret := make([]Flag, len(vfNames))
|
||||
for i, flName := range vfNames {
|
||||
ret[i] = fc.m[flName]
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
60
vendor/github.com/urfave/cli/v3/cli.go
generated
vendored
Normal file
60
vendor/github.com/urfave/cli/v3/cli.go
generated
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// Package cli provides a minimal framework for creating and organizing command line
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||
// cli application can be written as follows:
|
||||
//
|
||||
// func main() {
|
||||
// (&cli.Command{}).Run(context.Background(), os.Args)
|
||||
// }
|
||||
//
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
||||
//
|
||||
// func main() {
|
||||
// cmd := &cli.Command{
|
||||
// Name: "greet",
|
||||
// Usage: "say a greeting",
|
||||
// Action: func(c *cli.Context) error {
|
||||
// fmt.Println("Greetings")
|
||||
// return nil
|
||||
// },
|
||||
// }
|
||||
//
|
||||
// cmd.Run(context.Background(), os.Args)
|
||||
// }
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var isTracingOn = os.Getenv("URFAVE_CLI_TRACING") == "on"
|
||||
|
||||
func tracef(format string, a ...any) {
|
||||
if !isTracingOn {
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(format, "\n") {
|
||||
format = format + "\n"
|
||||
}
|
||||
|
||||
pc, file, line, _ := runtime.Caller(1)
|
||||
cf := runtime.FuncForPC(pc)
|
||||
|
||||
fmt.Fprintf(
|
||||
os.Stderr,
|
||||
strings.Join([]string{
|
||||
"## URFAVE CLI TRACE ",
|
||||
file,
|
||||
":",
|
||||
fmt.Sprintf("%v", line),
|
||||
" ",
|
||||
fmt.Sprintf("(%s)", cf.Name()),
|
||||
" ",
|
||||
format,
|
||||
}, ""),
|
||||
a...,
|
||||
)
|
||||
}
|
||||
563
vendor/github.com/urfave/cli/v3/command.go
generated
vendored
Normal file
563
vendor/github.com/urfave/cli/v3/command.go
generated
vendored
Normal file
|
|
@ -0,0 +1,563 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ignoreFlagPrefix is to ignore test flags when adding flags from other packages
|
||||
ignoreFlagPrefix = "test."
|
||||
|
||||
commandContextKey = contextKey("cli.context")
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
// Command contains everything needed to run an application that
|
||||
// accepts a string slice of arguments such as os.Args. A given
|
||||
// Command may contain Flags and sub-commands in Commands.
|
||||
type Command struct {
|
||||
// The name of the command
|
||||
Name string `json:"name"`
|
||||
// A list of aliases for the command
|
||||
Aliases []string `json:"aliases"`
|
||||
// A short description of the usage of this command
|
||||
Usage string `json:"usage"`
|
||||
// Text to override the USAGE section of help
|
||||
UsageText string `json:"usageText"`
|
||||
// A short description of the arguments of this command
|
||||
ArgsUsage string `json:"argsUsage"`
|
||||
// Version of the command
|
||||
Version string `json:"version"`
|
||||
// Longer explanation of how the command works
|
||||
Description string `json:"description"`
|
||||
// DefaultCommand is the (optional) name of a command
|
||||
// to run if no command names are passed as CLI arguments.
|
||||
DefaultCommand string `json:"defaultCommand"`
|
||||
// The category the command is part of
|
||||
Category string `json:"category"`
|
||||
// List of child commands
|
||||
Commands []*Command `json:"commands"`
|
||||
// List of flags to parse
|
||||
Flags []Flag `json:"flags"`
|
||||
// Boolean to hide built-in help command and help flag
|
||||
HideHelp bool `json:"hideHelp"`
|
||||
// Ignored if HideHelp is true.
|
||||
HideHelpCommand bool `json:"hideHelpCommand"`
|
||||
// Boolean to hide built-in version flag and the VERSION section of help
|
||||
HideVersion bool `json:"hideVersion"`
|
||||
// Boolean to enable shell completion commands
|
||||
EnableShellCompletion bool `json:"-"`
|
||||
// Shell Completion generation command name
|
||||
ShellCompletionCommandName string `json:"-"`
|
||||
// The function to call when checking for shell command completions
|
||||
ShellComplete ShellCompleteFunc `json:"-"`
|
||||
// The function to configure a shell completion command
|
||||
ConfigureShellCompletionCommand ConfigureShellCompletionCommand `json:"-"`
|
||||
// An action to execute before any subcommands are run, but after the context is ready
|
||||
// If a non-nil error is returned, no subcommands are run
|
||||
Before BeforeFunc `json:"-"`
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||
// It is run even if Action() panics
|
||||
After AfterFunc `json:"-"`
|
||||
// The function to call when this command is invoked
|
||||
Action ActionFunc `json:"-"`
|
||||
// Execute this function if the proper command cannot be found
|
||||
CommandNotFound CommandNotFoundFunc `json:"-"`
|
||||
// Execute this function if a usage error occurs.
|
||||
OnUsageError OnUsageErrorFunc `json:"-"`
|
||||
// Execute this function when an invalid flag is accessed from the context
|
||||
InvalidFlagAccessHandler InvalidFlagAccessFunc `json:"-"`
|
||||
// Boolean to hide this command from help or completion
|
||||
Hidden bool `json:"hidden"`
|
||||
// List of all authors who contributed (string or fmt.Stringer)
|
||||
// TODO: ~string | fmt.Stringer when interface unions are available
|
||||
Authors []any `json:"authors"`
|
||||
// Copyright of the binary if any
|
||||
Copyright string `json:"copyright"`
|
||||
// Reader reader to write input to (useful for tests)
|
||||
Reader io.Reader `json:"-"`
|
||||
// Writer writer to write output to
|
||||
Writer io.Writer `json:"-"`
|
||||
// ErrWriter writes error output
|
||||
ErrWriter io.Writer `json:"-"`
|
||||
// ExitErrHandler processes any error encountered while running a Command before it is
|
||||
// returned to the caller. If no function is provided, HandleExitCoder is used as the
|
||||
// default behavior.
|
||||
ExitErrHandler ExitErrHandlerFunc `json:"-"`
|
||||
// Other custom info
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
// Carries a function which returns app specific info.
|
||||
ExtraInfo func() map[string]string `json:"-"`
|
||||
// CustomRootCommandHelpTemplate the text template for app help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
CustomRootCommandHelpTemplate string `json:"-"`
|
||||
// SliceFlagSeparator is used to customize the separator for SliceFlag, the default is ","
|
||||
SliceFlagSeparator string `json:"sliceFlagSeparator"`
|
||||
// DisableSliceFlagSeparator is used to disable SliceFlagSeparator, the default is false
|
||||
DisableSliceFlagSeparator bool `json:"disableSliceFlagSeparator"`
|
||||
// Boolean to enable short-option handling so user can combine several
|
||||
// single-character bool arguments into one
|
||||
// i.e. foobar -o -v -> foobar -ov
|
||||
UseShortOptionHandling bool `json:"useShortOptionHandling"`
|
||||
// Enable suggestions for commands and flags
|
||||
Suggest bool `json:"suggest"`
|
||||
// Allows global flags set by libraries which use flag.XXXVar(...) directly
|
||||
// to be parsed through this library
|
||||
AllowExtFlags bool `json:"allowExtFlags"`
|
||||
// Treat all flags as normal arguments if true
|
||||
SkipFlagParsing bool `json:"skipFlagParsing"`
|
||||
// CustomHelpTemplate the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
CustomHelpTemplate string `json:"-"`
|
||||
// Use longest prefix match for commands
|
||||
PrefixMatchCommands bool `json:"prefixMatchCommands"`
|
||||
// Custom suggest command for matching
|
||||
SuggestCommandFunc SuggestCommandFunc `json:"-"`
|
||||
// Flag exclusion group
|
||||
MutuallyExclusiveFlags []MutuallyExclusiveFlags `json:"mutuallyExclusiveFlags"`
|
||||
// Arguments to parse for this command
|
||||
Arguments []Argument `json:"arguments"`
|
||||
// Whether to read arguments from stdin
|
||||
// applicable to root command only
|
||||
ReadArgsFromStdin bool `json:"readArgsFromStdin"`
|
||||
|
||||
// categories contains the categorized commands and is populated on app startup
|
||||
categories CommandCategories
|
||||
// flagCategories contains the categorized flags and is populated on app startup
|
||||
flagCategories FlagCategories
|
||||
// flags that have been applied in current parse
|
||||
appliedFlags []Flag
|
||||
// flags that have been set
|
||||
setFlags map[Flag]struct{}
|
||||
// The parent of this command. This value will be nil for the
|
||||
// command at the root of the graph.
|
||||
parent *Command
|
||||
// parsed args
|
||||
parsedArgs Args
|
||||
// track state of error handling
|
||||
isInError bool
|
||||
// track state of defaults
|
||||
didSetupDefaults bool
|
||||
// whether in shell completion mode
|
||||
shellCompletion bool
|
||||
}
|
||||
|
||||
// FullName returns the full name of the command.
|
||||
// For commands with parents this ensures that the parent commands
|
||||
// are part of the command path.
|
||||
func (cmd *Command) FullName() string {
|
||||
namePath := []string{}
|
||||
|
||||
if cmd.parent != nil {
|
||||
namePath = append(namePath, cmd.parent.FullName())
|
||||
}
|
||||
|
||||
return strings.Join(append(namePath, cmd.Name), " ")
|
||||
}
|
||||
|
||||
func (cmd *Command) Command(name string) *Command {
|
||||
for _, subCmd := range cmd.Commands {
|
||||
if subCmd.HasName(name) {
|
||||
return subCmd
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) checkHelp() bool {
|
||||
tracef("checking if help is wanted (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
return HelpFlag != nil && slices.ContainsFunc(HelpFlag.Names(), cmd.Bool)
|
||||
}
|
||||
|
||||
func (cmd *Command) allFlags() []Flag {
|
||||
var flags []Flag
|
||||
flags = append(flags, cmd.Flags...)
|
||||
for _, grpf := range cmd.MutuallyExclusiveFlags {
|
||||
for _, f1 := range grpf.Flags {
|
||||
flags = append(flags, f1...)
|
||||
}
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
// useShortOptionHandling traverses Lineage() for *any* ancestors
|
||||
// with UseShortOptionHandling
|
||||
func (cmd *Command) useShortOptionHandling() bool {
|
||||
for _, pCmd := range cmd.Lineage() {
|
||||
if pCmd.UseShortOptionHandling {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd *Command) suggestFlagFromError(err error, commandName string) (string, error) {
|
||||
fl, parseErr := flagFromError(err)
|
||||
if parseErr != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
flags := cmd.Flags
|
||||
hideHelp := cmd.hideHelp()
|
||||
|
||||
if commandName != "" {
|
||||
subCmd := cmd.Command(commandName)
|
||||
if subCmd == nil {
|
||||
return "", err
|
||||
}
|
||||
flags = subCmd.Flags
|
||||
hideHelp = hideHelp || subCmd.HideHelp
|
||||
}
|
||||
|
||||
suggestion := SuggestFlag(flags, fl, hideHelp)
|
||||
if len(suggestion) == 0 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf(SuggestDidYouMeanTemplate, suggestion) + "\n\n", nil
|
||||
}
|
||||
|
||||
// Names returns the names including short names and aliases.
|
||||
func (cmd *Command) Names() []string {
|
||||
return append([]string{cmd.Name}, cmd.Aliases...)
|
||||
}
|
||||
|
||||
// HasName returns true if Command.Name matches given name
|
||||
func (cmd *Command) HasName(name string) bool {
|
||||
return slices.Contains(cmd.Names(), name)
|
||||
}
|
||||
|
||||
// VisibleCategories returns a slice of categories and commands that are
|
||||
// Hidden=false
|
||||
func (cmd *Command) VisibleCategories() []CommandCategory {
|
||||
ret := []CommandCategory{}
|
||||
for _, category := range cmd.categories.Categories() {
|
||||
if visible := func() CommandCategory {
|
||||
if len(category.VisibleCommands()) > 0 {
|
||||
return category
|
||||
}
|
||||
return nil
|
||||
}(); visible != nil {
|
||||
ret = append(ret, visible)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
||||
func (cmd *Command) VisibleCommands() []*Command {
|
||||
var ret []*Command
|
||||
for _, command := range cmd.Commands {
|
||||
if command.Hidden || command.Name == helpName {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, command)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// VisibleFlagCategories returns a slice containing all the visible flag categories with the flags they contain
|
||||
func (cmd *Command) VisibleFlagCategories() []VisibleFlagCategory {
|
||||
if cmd.flagCategories == nil {
|
||||
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
|
||||
}
|
||||
return cmd.flagCategories.VisibleCategories()
|
||||
}
|
||||
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
||||
func (cmd *Command) VisibleFlags() []Flag {
|
||||
return visibleFlags(cmd.allFlags())
|
||||
}
|
||||
|
||||
func (cmd *Command) appendFlag(fl Flag) {
|
||||
if !hasFlag(cmd.Flags, fl) {
|
||||
cmd.Flags = append(cmd.Flags, fl)
|
||||
}
|
||||
}
|
||||
|
||||
// VisiblePersistentFlags returns a slice of [LocalFlag] with Persistent=true and Hidden=false.
|
||||
func (cmd *Command) VisiblePersistentFlags() []Flag {
|
||||
var flags []Flag
|
||||
for _, fl := range cmd.Root().Flags {
|
||||
pfl, ok := fl.(LocalFlag)
|
||||
if !ok || pfl.IsLocal() {
|
||||
continue
|
||||
}
|
||||
flags = append(flags, fl)
|
||||
}
|
||||
return visibleFlags(flags)
|
||||
}
|
||||
|
||||
func (cmd *Command) appendCommand(aCmd *Command) {
|
||||
if !slices.Contains(cmd.Commands, aCmd) {
|
||||
aCmd.parent = cmd
|
||||
cmd.Commands = append(cmd.Commands, aCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Command) handleExitCoder(ctx context.Context, err error) error {
|
||||
if cmd.parent != nil {
|
||||
return cmd.parent.handleExitCoder(ctx, err)
|
||||
}
|
||||
|
||||
if cmd.ExitErrHandler != nil {
|
||||
cmd.ExitErrHandler(ctx, cmd, err)
|
||||
return err
|
||||
}
|
||||
|
||||
HandleExitCoder(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (cmd *Command) argsWithDefaultCommand(oldArgs Args) Args {
|
||||
if cmd.DefaultCommand != "" {
|
||||
rawArgs := append([]string{cmd.DefaultCommand}, oldArgs.Slice()...)
|
||||
newArgs := &stringSliceArgs{v: rawArgs}
|
||||
|
||||
return newArgs
|
||||
}
|
||||
|
||||
return oldArgs
|
||||
}
|
||||
|
||||
// Root returns the Command at the root of the graph
|
||||
func (cmd *Command) Root() *Command {
|
||||
if cmd.parent == nil {
|
||||
return cmd
|
||||
}
|
||||
|
||||
return cmd.parent.Root()
|
||||
}
|
||||
|
||||
func (cmd *Command) set(fName string, f Flag, val string) error {
|
||||
cmd.setFlags[f] = struct{}{}
|
||||
if err := f.Set(fName, val); err != nil {
|
||||
return fmt.Errorf("invalid value %q for flag -%s: %v", val, fName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) lFlag(name string) Flag {
|
||||
for _, f := range cmd.allFlags() {
|
||||
if slices.Contains(f.Names(), name) {
|
||||
tracef("flag found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return f
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) lookupFlag(name string) Flag {
|
||||
for _, pCmd := range cmd.Lineage() {
|
||||
if f := pCmd.lFlag(name); f != nil {
|
||||
return f
|
||||
}
|
||||
}
|
||||
|
||||
tracef("flag NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
cmd.onInvalidFlag(context.TODO(), name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) checkRequiredFlag(f Flag) (bool, string) {
|
||||
if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() {
|
||||
flagName := f.Names()[0]
|
||||
if !f.IsSet() {
|
||||
return false, flagName
|
||||
}
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
func (cmd *Command) checkAllRequiredFlags() requiredFlagsErr {
|
||||
for pCmd := cmd; pCmd != nil; pCmd = pCmd.parent {
|
||||
if err := pCmd.checkRequiredFlags(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) checkRequiredFlags() requiredFlagsErr {
|
||||
tracef("checking for required flags (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
missingFlags := []string{}
|
||||
|
||||
for _, f := range cmd.appliedFlags {
|
||||
if ok, name := cmd.checkRequiredFlag(f); !ok {
|
||||
missingFlags = append(missingFlags, name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingFlags) != 0 {
|
||||
tracef("found missing required flags %[1]q (cmd=%[2]q)", missingFlags, cmd.Name)
|
||||
|
||||
return &errRequiredFlags{missingFlags: missingFlags}
|
||||
}
|
||||
|
||||
tracef("all required flags set (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cmd *Command) onInvalidFlag(ctx context.Context, name string) {
|
||||
for cmd != nil {
|
||||
if cmd.InvalidFlagAccessHandler != nil {
|
||||
cmd.InvalidFlagAccessHandler(ctx, cmd, name)
|
||||
break
|
||||
}
|
||||
cmd = cmd.parent
|
||||
}
|
||||
}
|
||||
|
||||
// NumFlags returns the number of flags set
|
||||
func (cmd *Command) NumFlags() int {
|
||||
tracef("numFlags numAppliedFlags %d", len(cmd.appliedFlags))
|
||||
count := 0
|
||||
for _, f := range cmd.appliedFlags {
|
||||
if f.IsSet() {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count // cmd.flagSet.NFlag()
|
||||
}
|
||||
|
||||
// Set sets a context flag to a value.
|
||||
func (cmd *Command) Set(name, value string) error {
|
||||
if f := cmd.lookupFlag(name); f != nil {
|
||||
return f.Set(name, value)
|
||||
}
|
||||
|
||||
return fmt.Errorf("no such flag -%s", name)
|
||||
}
|
||||
|
||||
// IsSet determines if the flag was actually set
|
||||
func (cmd *Command) IsSet(name string) bool {
|
||||
fl := cmd.lookupFlag(name)
|
||||
if fl == nil {
|
||||
tracef("flag with name %[1]q NOT found; assuming not set (cmd=%[2]q)", name, cmd.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
isSet := fl.IsSet()
|
||||
if isSet {
|
||||
tracef("flag with name %[1]q is set (cmd=%[2]q)", name, cmd.Name)
|
||||
} else {
|
||||
tracef("flag with name %[1]q is no set (cmd=%[2]q)", name, cmd.Name)
|
||||
}
|
||||
|
||||
return isSet
|
||||
}
|
||||
|
||||
// LocalFlagNames returns a slice of flag names used in this
|
||||
// command.
|
||||
func (cmd *Command) LocalFlagNames() []string {
|
||||
names := []string{}
|
||||
|
||||
// Check the flags which have been set via env or file
|
||||
for _, f := range cmd.allFlags() {
|
||||
if f.IsSet() {
|
||||
names = append(names, f.Names()...)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort out the duplicates since flag could be set via multiple
|
||||
// paths
|
||||
m := map[string]struct{}{}
|
||||
uniqNames := []string{}
|
||||
|
||||
for _, name := range names {
|
||||
if _, ok := m[name]; !ok {
|
||||
m[name] = struct{}{}
|
||||
uniqNames = append(uniqNames, name)
|
||||
}
|
||||
}
|
||||
|
||||
return uniqNames
|
||||
}
|
||||
|
||||
// FlagNames returns a slice of flag names used by the this command
|
||||
// and all of its parent commands.
|
||||
func (cmd *Command) FlagNames() []string {
|
||||
names := cmd.LocalFlagNames()
|
||||
|
||||
if cmd.parent != nil {
|
||||
names = append(cmd.parent.FlagNames(), names...)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// Lineage returns *this* command and all of its ancestor commands
|
||||
// in order from child to parent
|
||||
func (cmd *Command) Lineage() []*Command {
|
||||
lineage := []*Command{cmd}
|
||||
|
||||
if cmd.parent != nil {
|
||||
lineage = append(lineage, cmd.parent.Lineage()...)
|
||||
}
|
||||
|
||||
return lineage
|
||||
}
|
||||
|
||||
// Count returns the num of occurrences of this flag
|
||||
func (cmd *Command) Count(name string) int {
|
||||
if cf, ok := cmd.lookupFlag(name).(Countable); ok {
|
||||
return cf.Count()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Value returns the value of the flag corresponding to `name`
|
||||
func (cmd *Command) Value(name string) interface{} {
|
||||
if fs := cmd.lookupFlag(name); fs != nil {
|
||||
tracef("value found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return fs.Get()
|
||||
}
|
||||
|
||||
tracef("value NOT found for name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Args returns the command line arguments associated with the
|
||||
// command.
|
||||
func (cmd *Command) Args() Args {
|
||||
return cmd.parsedArgs
|
||||
}
|
||||
|
||||
// NArg returns the number of the command line arguments.
|
||||
func (cmd *Command) NArg() int {
|
||||
return cmd.Args().Len()
|
||||
}
|
||||
|
||||
func (cmd *Command) runFlagActions(ctx context.Context) error {
|
||||
tracef("runFlagActions")
|
||||
for fl := range cmd.setFlags {
|
||||
/*tracef("checking %v:%v", fl.Names(), fl.IsSet())
|
||||
if !fl.IsSet() {
|
||||
continue
|
||||
}*/
|
||||
|
||||
//if pf, ok := fl.(LocalFlag); ok && !pf.IsLocal() {
|
||||
// continue
|
||||
//}
|
||||
|
||||
if af, ok := fl.(ActionableFlag); ok {
|
||||
if err := af.RunAction(ctx, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
213
vendor/github.com/urfave/cli/v3/command_parse.go
generated
vendored
Normal file
213
vendor/github.com/urfave/cli/v3/command_parse.go
generated
vendored
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
providedButNotDefinedErrMsg = "flag provided but not defined: -"
|
||||
argumentNotProvidedErrMsg = "flag needs an argument: "
|
||||
)
|
||||
|
||||
// flagFromError tries to parse a provided flag from an error message. If the
|
||||
// parsing fails, it returns the input error and an empty string
|
||||
func flagFromError(err error) (string, error) {
|
||||
errStr := err.Error()
|
||||
trimmed := strings.TrimPrefix(errStr, providedButNotDefinedErrMsg)
|
||||
if errStr == trimmed {
|
||||
return "", err
|
||||
}
|
||||
return trimmed, nil
|
||||
}
|
||||
|
||||
func (cmd *Command) parseFlags(args Args) (Args, error) {
|
||||
tracef("parsing flags from arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
|
||||
|
||||
cmd.setFlags = map[Flag]struct{}{}
|
||||
cmd.appliedFlags = cmd.allFlags()
|
||||
|
||||
tracef("walking command lineage for persistent flags (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
for pCmd := cmd.parent; pCmd != nil; pCmd = pCmd.parent {
|
||||
tracef(
|
||||
"checking ancestor command=%[1]q for persistent flags (cmd=%[2]q)",
|
||||
pCmd.Name, cmd.Name,
|
||||
)
|
||||
|
||||
for _, fl := range pCmd.Flags {
|
||||
flNames := fl.Names()
|
||||
|
||||
pfl, ok := fl.(LocalFlag)
|
||||
if !ok || pfl.IsLocal() {
|
||||
tracef("skipping non-persistent flag %[1]q (cmd=%[2]q)", flNames, cmd.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
tracef(
|
||||
"checking for applying persistent flag=%[1]q pCmd=%[2]q (cmd=%[3]q)",
|
||||
flNames, pCmd.Name, cmd.Name,
|
||||
)
|
||||
|
||||
applyPersistentFlag := true
|
||||
|
||||
for _, name := range flNames {
|
||||
if cmd.lFlag(name) != nil {
|
||||
applyPersistentFlag = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !applyPersistentFlag {
|
||||
tracef("not applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
tracef("applying as persistent flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
|
||||
|
||||
tracef("appending to applied flags flag=%[1]q (cmd=%[2]q)", flNames, cmd.Name)
|
||||
cmd.appliedFlags = append(cmd.appliedFlags, fl)
|
||||
}
|
||||
}
|
||||
|
||||
tracef("parsing flags iteratively tail=%[1]q (cmd=%[2]q)", args.Tail(), cmd.Name)
|
||||
defer tracef("done parsing flags (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
posArgs := []string{}
|
||||
for rargs := args.Slice(); len(rargs) > 0; rargs = rargs[1:] {
|
||||
tracef("rearrange:1 (cmd=%[1]q) %[2]q", cmd.Name, rargs)
|
||||
|
||||
firstArg := strings.TrimSpace(rargs[0])
|
||||
if len(firstArg) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// stop parsing once we see a "--"
|
||||
if firstArg == "--" {
|
||||
posArgs = append(posArgs, rargs[1:]...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
|
||||
// handle positional args
|
||||
if firstArg[0] != '-' {
|
||||
// positional argument probably
|
||||
tracef("rearrange-3 (cmd=%[1]q) check %[2]q", cmd.Name, firstArg)
|
||||
|
||||
// if there is a command by that name let the command handle the
|
||||
// rest of the parsing
|
||||
if cmd.Command(firstArg) != nil {
|
||||
posArgs = append(posArgs, rargs...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
|
||||
posArgs = append(posArgs, firstArg)
|
||||
continue
|
||||
}
|
||||
|
||||
numMinuses := 1
|
||||
// this is same as firstArg == "-"
|
||||
if len(firstArg) == 1 {
|
||||
posArgs = append(posArgs, firstArg)
|
||||
break
|
||||
}
|
||||
|
||||
shortOptionHandling := cmd.useShortOptionHandling()
|
||||
|
||||
// stop parsing -- as short flags
|
||||
if firstArg[1] == '-' {
|
||||
numMinuses++
|
||||
shortOptionHandling = false
|
||||
} else if !unicode.IsLetter(rune(firstArg[1])) {
|
||||
// this is not a flag
|
||||
tracef("parseFlags not a unicode letter. Stop parsing")
|
||||
posArgs = append(posArgs, rargs...)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
|
||||
tracef("parseFlags (shortOptionHandling=%[1]q)", shortOptionHandling)
|
||||
|
||||
flagName := firstArg[numMinuses:]
|
||||
flagVal := ""
|
||||
tracef("flagName:1 (fName=%[1]q)", flagName)
|
||||
if index := strings.Index(flagName, "="); index != -1 {
|
||||
flagVal = flagName[index+1:]
|
||||
flagName = flagName[:index]
|
||||
}
|
||||
|
||||
tracef("flagName:2 (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
|
||||
|
||||
f := cmd.lookupFlag(flagName)
|
||||
// found a flag matching given flagName
|
||||
if f != nil {
|
||||
tracef("Trying flag type (fName=%[1]q) (type=%[2]T)", flagName, f)
|
||||
if fb, ok := f.(boolFlag); ok && fb.IsBoolFlag() {
|
||||
if flagVal == "" {
|
||||
flagVal = "true"
|
||||
}
|
||||
tracef("parse Apply bool flag (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
|
||||
if err := cmd.set(flagName, f, flagVal); err != nil {
|
||||
return &stringSliceArgs{posArgs}, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
tracef("processing non bool flag (fName=%[1]q)", flagName)
|
||||
// not a bool flag so need to get the next arg
|
||||
if flagVal == "" {
|
||||
if len(rargs) == 1 {
|
||||
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, firstArg)
|
||||
}
|
||||
flagVal = rargs[1]
|
||||
rargs = rargs[1:]
|
||||
}
|
||||
|
||||
tracef("setting non bool flag (fName=%[1]q) (fVal=%[2]q)", flagName, flagVal)
|
||||
if err := cmd.set(flagName, f, flagVal); err != nil {
|
||||
return &stringSliceArgs{posArgs}, err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// no flag lookup found and short handling is disabled
|
||||
if !shortOptionHandling {
|
||||
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
|
||||
}
|
||||
|
||||
// try to split the flags
|
||||
for index, c := range flagName {
|
||||
tracef("processing flag (fName=%[1]q)", string(c))
|
||||
if sf := cmd.lookupFlag(string(c)); sf == nil {
|
||||
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", providedButNotDefinedErrMsg, flagName)
|
||||
} else if fb, ok := sf.(boolFlag); ok && fb.IsBoolFlag() {
|
||||
fv := flagVal
|
||||
if index == (len(flagName)-1) && flagVal == "" {
|
||||
fv = "true"
|
||||
}
|
||||
if fv == "" {
|
||||
fv = "true"
|
||||
}
|
||||
if err := cmd.set(flagName, sf, fv); err != nil {
|
||||
tracef("processing flag.2 (fName=%[1]q)", string(c))
|
||||
return &stringSliceArgs{posArgs}, err
|
||||
}
|
||||
} else if index == len(flagName)-1 { // last flag can take an arg
|
||||
if flagVal == "" {
|
||||
if len(rargs) == 1 {
|
||||
return &stringSliceArgs{posArgs}, fmt.Errorf("%s%s", argumentNotProvidedErrMsg, string(c))
|
||||
}
|
||||
flagVal = rargs[1]
|
||||
}
|
||||
tracef("parseFlags (flagName %[1]q) (flagVal %[2]q)", flagName, flagVal)
|
||||
if err := cmd.set(flagName, sf, flagVal); err != nil {
|
||||
tracef("processing flag.4 (fName=%[1]q)", string(c))
|
||||
return &stringSliceArgs{posArgs}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracef("returning-2 (cmd=%[1]q) args %[2]q", cmd.Name, posArgs)
|
||||
return &stringSliceArgs{posArgs}, nil
|
||||
}
|
||||
351
vendor/github.com/urfave/cli/v3/command_run.go
generated
vendored
Normal file
351
vendor/github.com/urfave/cli/v3/command_run.go
generated
vendored
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"slices"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func (cmd *Command) parseArgsFromStdin() ([]string, error) {
|
||||
type state int
|
||||
const (
|
||||
stateSearchForToken state = -1
|
||||
stateSearchForString state = 0
|
||||
)
|
||||
|
||||
st := stateSearchForToken
|
||||
linenum := 1
|
||||
token := ""
|
||||
args := []string{}
|
||||
|
||||
breader := bufio.NewReader(cmd.Reader)
|
||||
|
||||
outer:
|
||||
for {
|
||||
ch, _, err := breader.ReadRune()
|
||||
if err == io.EOF {
|
||||
switch st {
|
||||
case stateSearchForToken:
|
||||
if token != "--" {
|
||||
args = append(args, token)
|
||||
}
|
||||
case stateSearchForString:
|
||||
// make sure string is not empty
|
||||
for _, t := range token {
|
||||
if !unicode.IsSpace(t) {
|
||||
args = append(args, token)
|
||||
}
|
||||
}
|
||||
}
|
||||
break outer
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch st {
|
||||
case stateSearchForToken:
|
||||
if unicode.IsSpace(ch) || ch == '"' {
|
||||
if ch == '\n' {
|
||||
linenum++
|
||||
}
|
||||
if token != "" {
|
||||
// end the processing here
|
||||
if token == "--" {
|
||||
break outer
|
||||
}
|
||||
args = append(args, token)
|
||||
token = ""
|
||||
}
|
||||
if ch == '"' {
|
||||
st = stateSearchForString
|
||||
}
|
||||
continue
|
||||
}
|
||||
token += string(ch)
|
||||
case stateSearchForString:
|
||||
if ch != '"' {
|
||||
token += string(ch)
|
||||
} else {
|
||||
if token != "" {
|
||||
args = append(args, token)
|
||||
token = ""
|
||||
}
|
||||
/*else {
|
||||
//TODO. Should we pass in empty strings ?
|
||||
}*/
|
||||
st = stateSearchForToken
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tracef("parsed stdin args as %v (cmd=%[2]q)", args, cmd.Name)
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// Run is the entry point to the command graph. The positional
|
||||
// arguments are parsed according to the Flag and Command
|
||||
// definitions and the matching Action functions are run.
|
||||
func (cmd *Command) Run(ctx context.Context, osArgs []string) (deferErr error) {
|
||||
_, deferErr = cmd.run(ctx, osArgs)
|
||||
return
|
||||
}
|
||||
|
||||
func (cmd *Command) run(ctx context.Context, osArgs []string) (_ context.Context, deferErr error) {
|
||||
tracef("running with arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
|
||||
cmd.setupDefaults(osArgs)
|
||||
|
||||
if v, ok := ctx.Value(commandContextKey).(*Command); ok {
|
||||
tracef("setting parent (cmd=%[1]q) command from context.Context value (cmd=%[2]q)", v.Name, cmd.Name)
|
||||
cmd.parent = v
|
||||
}
|
||||
|
||||
if cmd.parent == nil {
|
||||
if cmd.ReadArgsFromStdin {
|
||||
if args, err := cmd.parseArgsFromStdin(); err != nil {
|
||||
return ctx, err
|
||||
} else {
|
||||
osArgs = append(osArgs, args...)
|
||||
}
|
||||
}
|
||||
// handle the completion flag separately from the flagset since
|
||||
// completion could be attempted after a flag, but before its value was put
|
||||
// on the command line. this causes the flagset to interpret the completion
|
||||
// flag name as the value of the flag before it which is undesirable
|
||||
// note that we can only do this because the shell autocomplete function
|
||||
// always appends the completion flag at the end of the command
|
||||
tracef("checking osArgs %v (cmd=%[2]q)", osArgs, cmd.Name)
|
||||
cmd.shellCompletion, osArgs = checkShellCompleteFlag(cmd, osArgs)
|
||||
|
||||
tracef("setting cmd.shellCompletion=%[1]v from checkShellCompleteFlag (cmd=%[2]q)", cmd.shellCompletion && cmd.EnableShellCompletion, cmd.Name)
|
||||
cmd.shellCompletion = cmd.EnableShellCompletion && cmd.shellCompletion
|
||||
}
|
||||
|
||||
tracef("using post-checkShellCompleteFlag arguments %[1]q (cmd=%[2]q)", osArgs, cmd.Name)
|
||||
|
||||
tracef("setting self as cmd in context (cmd=%[1]q)", cmd.Name)
|
||||
ctx = context.WithValue(ctx, commandContextKey, cmd)
|
||||
|
||||
if cmd.parent == nil {
|
||||
cmd.setupCommandGraph()
|
||||
}
|
||||
|
||||
var rargs Args = &stringSliceArgs{v: osArgs}
|
||||
for _, f := range cmd.allFlags() {
|
||||
if err := f.PreParse(); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
|
||||
var args Args = &stringSliceArgs{rargs.Tail()}
|
||||
var err error
|
||||
|
||||
if cmd.SkipFlagParsing {
|
||||
tracef("skipping flag parsing (cmd=%[1]q)", cmd.Name)
|
||||
cmd.parsedArgs = args
|
||||
} else {
|
||||
cmd.parsedArgs, err = cmd.parseFlags(args)
|
||||
}
|
||||
|
||||
tracef("using post-parse arguments %[1]q (cmd=%[2]q)", args, cmd.Name)
|
||||
|
||||
if checkCompletions(ctx, cmd) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
tracef("setting deferErr from %[1]q (cmd=%[2]q)", err, cmd.Name)
|
||||
deferErr = err
|
||||
|
||||
cmd.isInError = true
|
||||
if cmd.OnUsageError != nil {
|
||||
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
||||
err = cmd.handleExitCoder(ctx, err)
|
||||
return ctx, err
|
||||
}
|
||||
fmt.Fprintf(cmd.Root().ErrWriter, "Incorrect Usage: %s\n\n", err.Error())
|
||||
if cmd.Suggest {
|
||||
if suggestion, err := cmd.suggestFlagFromError(err, ""); err == nil {
|
||||
fmt.Fprintf(cmd.Root().ErrWriter, "%s", suggestion)
|
||||
}
|
||||
}
|
||||
if !cmd.hideHelp() {
|
||||
if cmd.parent == nil {
|
||||
tracef("running ShowRootCommandHelp")
|
||||
if err := ShowRootCommandHelp(cmd); err != nil {
|
||||
tracef("SILENTLY IGNORING ERROR running ShowRootCommandHelp %[1]v (cmd=%[2]q)", err, cmd.Name)
|
||||
}
|
||||
} else {
|
||||
tracef("running ShowCommandHelp with %[1]q", cmd.Name)
|
||||
if err := ShowCommandHelp(ctx, cmd, cmd.Name); err != nil {
|
||||
tracef("SILENTLY IGNORING ERROR running ShowCommandHelp with %[1]q %[2]v", cmd.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
if cmd.checkHelp() {
|
||||
return ctx, helpCommandAction(ctx, cmd)
|
||||
} else {
|
||||
tracef("no help is wanted (cmd=%[1]q)", cmd.Name)
|
||||
}
|
||||
|
||||
if cmd.parent == nil && !cmd.HideVersion && checkVersion(cmd) {
|
||||
ShowVersion(cmd)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
for _, flag := range cmd.allFlags() {
|
||||
if err := flag.PostParse(); err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.After != nil && !cmd.Root().shellCompletion {
|
||||
defer func() {
|
||||
if err := cmd.After(ctx, cmd); err != nil {
|
||||
err = cmd.handleExitCoder(ctx, err)
|
||||
|
||||
if deferErr != nil {
|
||||
deferErr = newMultiError(deferErr, err)
|
||||
} else {
|
||||
deferErr = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for _, grp := range cmd.MutuallyExclusiveFlags {
|
||||
if err := grp.check(cmd); err != nil {
|
||||
if cmd.OnUsageError != nil {
|
||||
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
||||
} else {
|
||||
_ = ShowSubcommandHelp(cmd)
|
||||
}
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
|
||||
var subCmd *Command
|
||||
if cmd.parsedArgs.Present() {
|
||||
tracef("checking positional args %[1]q (cmd=%[2]q)", cmd.parsedArgs, cmd.Name)
|
||||
|
||||
name := cmd.parsedArgs.First()
|
||||
|
||||
tracef("using first positional argument as sub-command name=%[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
|
||||
if cmd.SuggestCommandFunc != nil && name != "--" {
|
||||
name = cmd.SuggestCommandFunc(cmd.Commands, name)
|
||||
}
|
||||
subCmd = cmd.Command(name)
|
||||
if subCmd == nil {
|
||||
hasDefault := cmd.DefaultCommand != ""
|
||||
isFlagName := slices.Contains(cmd.FlagNames(), name)
|
||||
|
||||
if hasDefault {
|
||||
tracef("using default command=%[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
|
||||
}
|
||||
|
||||
if isFlagName || hasDefault {
|
||||
argsWithDefault := cmd.argsWithDefaultCommand(args)
|
||||
tracef("using default command args=%[1]q (cmd=%[2]q)", argsWithDefault, cmd.Name)
|
||||
if !reflect.DeepEqual(args, argsWithDefault) {
|
||||
subCmd = cmd.Command(argsWithDefault.First())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if cmd.parent == nil && cmd.DefaultCommand != "" {
|
||||
tracef("no positional args present; checking default command %[1]q (cmd=%[2]q)", cmd.DefaultCommand, cmd.Name)
|
||||
|
||||
if dc := cmd.Command(cmd.DefaultCommand); dc != cmd {
|
||||
subCmd = dc
|
||||
}
|
||||
}
|
||||
|
||||
// If a subcommand has been resolved, let it handle the remaining execution.
|
||||
if subCmd != nil {
|
||||
tracef("running sub-command %[1]q with arguments %[2]q (cmd=%[3]q)", subCmd.Name, cmd.Args(), cmd.Name)
|
||||
|
||||
// It is important that we overwrite the ctx variable in the current
|
||||
// function so any defer'd functions use the new context returned
|
||||
// from the sub command.
|
||||
ctx, err = subCmd.run(ctx, cmd.Args().Slice())
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// This code path is the innermost command execution. Here we actually
|
||||
// perform the command action.
|
||||
//
|
||||
// First, resolve the chain of nested commands up to the parent.
|
||||
var cmdChain []*Command
|
||||
for p := cmd; p != nil; p = p.parent {
|
||||
cmdChain = append(cmdChain, p)
|
||||
}
|
||||
slices.Reverse(cmdChain)
|
||||
|
||||
// Run Before actions in order.
|
||||
for _, cmd := range cmdChain {
|
||||
if cmd.Before == nil {
|
||||
continue
|
||||
}
|
||||
if bctx, err := cmd.Before(ctx, cmd); err != nil {
|
||||
deferErr = cmd.handleExitCoder(ctx, err)
|
||||
return ctx, deferErr
|
||||
} else if bctx != nil {
|
||||
ctx = bctx
|
||||
}
|
||||
}
|
||||
|
||||
// Run flag actions in order.
|
||||
// These take a context, so this has to happen after Before actions.
|
||||
for _, cmd := range cmdChain {
|
||||
tracef("running flag actions (cmd=%[1]q)", cmd.Name)
|
||||
if err := cmd.runFlagActions(ctx); err != nil {
|
||||
deferErr = cmd.handleExitCoder(ctx, err)
|
||||
return ctx, deferErr
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.checkAllRequiredFlags(); err != nil {
|
||||
cmd.isInError = true
|
||||
if cmd.OnUsageError != nil {
|
||||
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
||||
} else {
|
||||
_ = ShowSubcommandHelp(cmd)
|
||||
}
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// Run the command action.
|
||||
if len(cmd.Arguments) > 0 {
|
||||
rargs := cmd.Args().Slice()
|
||||
tracef("calling argparse with %[1]v", rargs)
|
||||
for _, arg := range cmd.Arguments {
|
||||
var err error
|
||||
rargs, err = arg.Parse(rargs)
|
||||
if err != nil {
|
||||
tracef("calling with %[1]v (cmd=%[2]q)", err, cmd.Name)
|
||||
if cmd.OnUsageError != nil {
|
||||
err = cmd.OnUsageError(ctx, cmd, err, cmd.parent != nil)
|
||||
}
|
||||
err = cmd.handleExitCoder(ctx, err)
|
||||
return ctx, err
|
||||
}
|
||||
}
|
||||
cmd.parsedArgs = &stringSliceArgs{v: rargs}
|
||||
}
|
||||
|
||||
if err := cmd.Action(ctx, cmd); err != nil {
|
||||
tracef("calling handleExitCoder with %[1]v (cmd=%[2]q)", err, cmd.Name)
|
||||
deferErr = cmd.handleExitCoder(ctx, err)
|
||||
}
|
||||
|
||||
tracef("returning deferErr (cmd=%[1]q) %[2]q", cmd.Name, deferErr)
|
||||
return ctx, deferErr
|
||||
}
|
||||
214
vendor/github.com/urfave/cli/v3/command_setup.go
generated
vendored
Normal file
214
vendor/github.com/urfave/cli/v3/command_setup.go
generated
vendored
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (cmd *Command) setupDefaults(osArgs []string) {
|
||||
if cmd.didSetupDefaults {
|
||||
tracef("already did setup (cmd=%[1]q)", cmd.Name)
|
||||
return
|
||||
}
|
||||
|
||||
cmd.didSetupDefaults = true
|
||||
|
||||
isRoot := cmd.parent == nil
|
||||
tracef("isRoot? %[1]v (cmd=%[2]q)", isRoot, cmd.Name)
|
||||
|
||||
if cmd.ShellComplete == nil {
|
||||
tracef("setting default ShellComplete (cmd=%[1]q)", cmd.Name)
|
||||
cmd.ShellComplete = DefaultCompleteWithFlags
|
||||
}
|
||||
|
||||
if cmd.Name == "" && isRoot {
|
||||
name := filepath.Base(osArgs[0])
|
||||
tracef("setting cmd.Name from first arg basename (cmd=%[1]q)", name)
|
||||
cmd.Name = name
|
||||
}
|
||||
|
||||
if cmd.Usage == "" && isRoot {
|
||||
tracef("setting default Usage (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Usage = "A new cli application"
|
||||
}
|
||||
|
||||
if cmd.Version == "" {
|
||||
tracef("setting HideVersion=true due to empty Version (cmd=%[1]q)", cmd.Name)
|
||||
cmd.HideVersion = true
|
||||
}
|
||||
|
||||
if cmd.Action == nil {
|
||||
tracef("setting default Action as help command action (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Action = helpCommandAction
|
||||
}
|
||||
|
||||
if cmd.Reader == nil {
|
||||
tracef("setting default Reader as os.Stdin (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Reader = os.Stdin
|
||||
}
|
||||
|
||||
if cmd.Writer == nil {
|
||||
tracef("setting default Writer as os.Stdout (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Writer = os.Stdout
|
||||
}
|
||||
|
||||
if cmd.ErrWriter == nil {
|
||||
tracef("setting default ErrWriter as os.Stderr (cmd=%[1]q)", cmd.Name)
|
||||
cmd.ErrWriter = os.Stderr
|
||||
}
|
||||
|
||||
if cmd.AllowExtFlags {
|
||||
tracef("visiting all flags given AllowExtFlags=true (cmd=%[1]q)", cmd.Name)
|
||||
// add global flags added by other packages
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
// skip test flags
|
||||
if !strings.HasPrefix(f.Name, ignoreFlagPrefix) {
|
||||
cmd.Flags = append(cmd.Flags, &extFlag{f})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
for _, subCmd := range cmd.Commands {
|
||||
tracef("setting sub-command (cmd=%[1]q) parent as self (cmd=%[2]q)", subCmd.Name, cmd.Name)
|
||||
subCmd.parent = cmd
|
||||
}
|
||||
|
||||
cmd.ensureHelp()
|
||||
|
||||
if !cmd.HideVersion && isRoot {
|
||||
tracef("appending version flag (cmd=%[1]q)", cmd.Name)
|
||||
cmd.appendFlag(VersionFlag)
|
||||
}
|
||||
|
||||
if cmd.PrefixMatchCommands && cmd.SuggestCommandFunc == nil {
|
||||
tracef("setting default SuggestCommandFunc (cmd=%[1]q)", cmd.Name)
|
||||
cmd.SuggestCommandFunc = suggestCommand
|
||||
}
|
||||
|
||||
if isRoot && cmd.EnableShellCompletion || cmd.ConfigureShellCompletionCommand != nil {
|
||||
completionCommand := buildCompletionCommand(cmd.Name)
|
||||
|
||||
if cmd.ShellCompletionCommandName != "" {
|
||||
tracef(
|
||||
"setting completion command name (%[1]q) from "+
|
||||
"cmd.ShellCompletionCommandName (cmd=%[2]q)",
|
||||
cmd.ShellCompletionCommandName, cmd.Name,
|
||||
)
|
||||
completionCommand.Name = cmd.ShellCompletionCommandName
|
||||
}
|
||||
|
||||
tracef("appending completionCommand (cmd=%[1]q)", cmd.Name)
|
||||
cmd.appendCommand(completionCommand)
|
||||
if cmd.ConfigureShellCompletionCommand != nil {
|
||||
cmd.ConfigureShellCompletionCommand(completionCommand)
|
||||
}
|
||||
}
|
||||
|
||||
tracef("setting command categories (cmd=%[1]q)", cmd.Name)
|
||||
cmd.categories = newCommandCategories()
|
||||
|
||||
for _, subCmd := range cmd.Commands {
|
||||
cmd.categories.AddCommand(subCmd.Category, subCmd)
|
||||
}
|
||||
|
||||
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
|
||||
sort.Sort(cmd.categories.(*commandCategories))
|
||||
|
||||
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
|
||||
for _, grp := range cmd.MutuallyExclusiveFlags {
|
||||
grp.propagateCategory()
|
||||
}
|
||||
|
||||
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
|
||||
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
|
||||
|
||||
if cmd.Metadata == nil {
|
||||
tracef("setting default Metadata (cmd=%[1]q)", cmd.Name)
|
||||
cmd.Metadata = map[string]any{}
|
||||
}
|
||||
|
||||
if len(cmd.SliceFlagSeparator) != 0 {
|
||||
tracef("setting defaultSliceFlagSeparator from cmd.SliceFlagSeparator (cmd=%[1]q)", cmd.Name)
|
||||
defaultSliceFlagSeparator = cmd.SliceFlagSeparator
|
||||
}
|
||||
|
||||
tracef("setting disableSliceFlagSeparator from cmd.DisableSliceFlagSeparator (cmd=%[1]q)", cmd.Name)
|
||||
disableSliceFlagSeparator = cmd.DisableSliceFlagSeparator
|
||||
|
||||
cmd.setFlags = map[Flag]struct{}{}
|
||||
}
|
||||
|
||||
func (cmd *Command) setupCommandGraph() {
|
||||
tracef("setting up command graph (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
for _, subCmd := range cmd.Commands {
|
||||
subCmd.parent = cmd
|
||||
subCmd.setupSubcommand()
|
||||
subCmd.setupCommandGraph()
|
||||
}
|
||||
}
|
||||
|
||||
func (cmd *Command) setupSubcommand() {
|
||||
tracef("setting up self as sub-command (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
cmd.ensureHelp()
|
||||
|
||||
tracef("setting command categories (cmd=%[1]q)", cmd.Name)
|
||||
cmd.categories = newCommandCategories()
|
||||
|
||||
for _, subCmd := range cmd.Commands {
|
||||
cmd.categories.AddCommand(subCmd.Category, subCmd)
|
||||
}
|
||||
|
||||
tracef("sorting command categories (cmd=%[1]q)", cmd.Name)
|
||||
sort.Sort(cmd.categories.(*commandCategories))
|
||||
|
||||
tracef("setting category on mutually exclusive flags (cmd=%[1]q)", cmd.Name)
|
||||
for _, grp := range cmd.MutuallyExclusiveFlags {
|
||||
grp.propagateCategory()
|
||||
}
|
||||
|
||||
tracef("setting flag categories (cmd=%[1]q)", cmd.Name)
|
||||
cmd.flagCategories = newFlagCategoriesFromFlags(cmd.allFlags())
|
||||
}
|
||||
|
||||
func (cmd *Command) hideHelp() bool {
|
||||
tracef("hide help (cmd=%[1]q)", cmd.Name)
|
||||
for c := cmd; c != nil; c = c.parent {
|
||||
if c.HideHelp {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (cmd *Command) ensureHelp() {
|
||||
tracef("ensuring help (cmd=%[1]q)", cmd.Name)
|
||||
|
||||
helpCommand := buildHelpCommand(true)
|
||||
|
||||
if !cmd.hideHelp() {
|
||||
if cmd.Command(helpCommand.Name) == nil {
|
||||
if !cmd.HideHelpCommand {
|
||||
tracef("appending helpCommand (cmd=%[1]q)", cmd.Name)
|
||||
cmd.appendCommand(helpCommand)
|
||||
}
|
||||
}
|
||||
|
||||
if HelpFlag != nil {
|
||||
// TODO need to remove hack
|
||||
if hf, ok := HelpFlag.(*BoolFlag); ok {
|
||||
hf.applied = false
|
||||
hf.hasBeenSet = false
|
||||
hf.Value = false
|
||||
hf.value = nil
|
||||
}
|
||||
tracef("appending HelpFlag (cmd=%[1]q)", cmd.Name)
|
||||
cmd.appendFlag(HelpFlag)
|
||||
}
|
||||
}
|
||||
}
|
||||
100
vendor/github.com/urfave/cli/v3/completion.go
generated
vendored
Normal file
100
vendor/github.com/urfave/cli/v3/completion.go
generated
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
completionCommandName = "completion"
|
||||
|
||||
// This flag is supposed to only be used by the completion script itself to generate completions on the fly.
|
||||
completionFlag = "--generate-shell-completion"
|
||||
)
|
||||
|
||||
type renderCompletion func(cmd *Command, appName string) (string, error)
|
||||
|
||||
var (
|
||||
//go:embed autocomplete
|
||||
autoCompleteFS embed.FS
|
||||
|
||||
shellCompletions = map[string]renderCompletion{
|
||||
"bash": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/bash_autocomplete")
|
||||
return fmt.Sprintf(string(b), appName), err
|
||||
},
|
||||
"zsh": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/zsh_autocomplete")
|
||||
return fmt.Sprintf(string(b), appName), err
|
||||
},
|
||||
"fish": func(c *Command, appName string) (string, error) {
|
||||
return c.Root().ToFishCompletion()
|
||||
},
|
||||
"pwsh": func(c *Command, appName string) (string, error) {
|
||||
b, err := autoCompleteFS.ReadFile("autocomplete/powershell_autocomplete.ps1")
|
||||
return string(b), err
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const completionDescription = `Output shell completion script for bash, zsh, fish, or Powershell.
|
||||
Source the output to enable completion.
|
||||
|
||||
# .bashrc
|
||||
source <($COMMAND completion bash)
|
||||
|
||||
# .zshrc
|
||||
source <($COMMAND completion zsh)
|
||||
|
||||
# fish
|
||||
$COMMAND completion fish > ~/.config/fish/completions/$COMMAND.fish
|
||||
|
||||
# Powershell
|
||||
Output the script to path/to/autocomplete/$COMMAND.ps1 an run it.
|
||||
`
|
||||
|
||||
func buildCompletionCommand(appName string) *Command {
|
||||
return &Command{
|
||||
Name: completionCommandName,
|
||||
Hidden: true,
|
||||
Usage: "Output shell completion script for bash, zsh, fish, or Powershell",
|
||||
Description: strings.ReplaceAll(completionDescription, "$COMMAND", appName),
|
||||
Action: func(ctx context.Context, cmd *Command) error {
|
||||
return printShellCompletion(ctx, cmd, appName)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func printShellCompletion(_ context.Context, cmd *Command, appName string) error {
|
||||
var shells []string
|
||||
for k := range shellCompletions {
|
||||
shells = append(shells, k)
|
||||
}
|
||||
|
||||
sort.Strings(shells)
|
||||
|
||||
if cmd.Args().Len() == 0 {
|
||||
return Exit(fmt.Sprintf("no shell provided for completion command. available shells are %+v", shells), 1)
|
||||
}
|
||||
s := cmd.Args().First()
|
||||
|
||||
renderCompletion, ok := shellCompletions[s]
|
||||
if !ok {
|
||||
return Exit(fmt.Sprintf("unknown shell %s, available shells are %+v", s, shells), 1)
|
||||
}
|
||||
|
||||
completionScript, err := renderCompletion(cmd, appName)
|
||||
if err != nil {
|
||||
return Exit(err, 1)
|
||||
}
|
||||
|
||||
_, err = cmd.Writer.Write([]byte(completionScript))
|
||||
if err != nil {
|
||||
return Exit(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
125
vendor/github.com/urfave/cli/v3/docs.go
generated
vendored
Normal file
125
vendor/github.com/urfave/cli/v3/docs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func prefixFor(name string) (prefix string) {
|
||||
if len(name) == 1 {
|
||||
prefix = "-"
|
||||
} else {
|
||||
prefix = "--"
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Returns the placeholder, if any, and the unquoted usage string.
|
||||
func unquoteUsage(usage string) (string, string) {
|
||||
for i := 0; i < len(usage); i++ {
|
||||
if usage[i] == '`' {
|
||||
for j := i + 1; j < len(usage); j++ {
|
||||
if usage[j] == '`' {
|
||||
name := usage[i+1 : j]
|
||||
usage = usage[:i] + name + usage[j+1:]
|
||||
return name, usage
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return "", usage
|
||||
}
|
||||
|
||||
func prefixedNames(names []string, placeholder string) string {
|
||||
var prefixed string
|
||||
for i, name := range names {
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
prefixed += prefixFor(name) + name
|
||||
if placeholder != "" {
|
||||
prefixed += " " + placeholder
|
||||
}
|
||||
if i < len(names)-1 {
|
||||
prefixed += ", "
|
||||
}
|
||||
}
|
||||
return prefixed
|
||||
}
|
||||
|
||||
func envFormat(envVars []string, prefix, sep, suffix string) string {
|
||||
if len(envVars) > 0 {
|
||||
return fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func defaultEnvFormat(envVars []string) string {
|
||||
return envFormat(envVars, "$", ", $", "")
|
||||
}
|
||||
|
||||
func withEnvHint(envVars []string, str string) string {
|
||||
envText := ""
|
||||
if runtime.GOOS != "windows" || os.Getenv("PSHOME") != "" {
|
||||
envText = defaultEnvFormat(envVars)
|
||||
} else {
|
||||
envText = envFormat(envVars, "%", "%, %", "%")
|
||||
}
|
||||
return str + envText
|
||||
}
|
||||
|
||||
func withFileHint(filePath, str string) string {
|
||||
fileText := ""
|
||||
if filePath != "" {
|
||||
fileText = fmt.Sprintf(" [%s]", filePath)
|
||||
}
|
||||
return str + fileText
|
||||
}
|
||||
|
||||
func formatDefault(format string) string {
|
||||
return " (default: " + format + ")"
|
||||
}
|
||||
|
||||
func stringifyFlag(f Flag) string {
|
||||
// enforce DocGeneration interface on flags to avoid reflection
|
||||
df, ok := f.(DocGenerationFlag)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
placeholder, usage := unquoteUsage(df.GetUsage())
|
||||
needsPlaceholder := df.TakesValue()
|
||||
// if needsPlaceholder is true, placeholder is empty
|
||||
if needsPlaceholder && placeholder == "" {
|
||||
// try to get type from flag
|
||||
if tname := df.TypeName(); tname != "" {
|
||||
placeholder = tname
|
||||
} else {
|
||||
placeholder = defaultPlaceholder
|
||||
}
|
||||
}
|
||||
|
||||
defaultValueString := ""
|
||||
|
||||
// don't print default text for required flags
|
||||
if rf, ok := f.(RequiredFlag); !ok || !rf.IsRequired() {
|
||||
isVisible := df.IsDefaultVisible()
|
||||
if s := df.GetDefaultText(); isVisible && s != "" {
|
||||
defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
|
||||
}
|
||||
}
|
||||
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultValueString)
|
||||
|
||||
pn := prefixedNames(f.Names(), placeholder)
|
||||
sliceFlag, ok := f.(DocGenerationMultiValueFlag)
|
||||
if ok && sliceFlag.IsMultiValueFlag() {
|
||||
pn = pn + " [ " + pn + " ]"
|
||||
}
|
||||
|
||||
return withEnvHint(df.GetEnvVars(), fmt.Sprintf("%s\t%s", pn, usageWithDefault))
|
||||
}
|
||||
184
vendor/github.com/urfave/cli/v3/errors.go
generated
vendored
Normal file
184
vendor/github.com/urfave/cli/v3/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
||||
var OsExiter = os.Exit
|
||||
|
||||
// ErrWriter is used to write errors to the user. This can be anything
|
||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
||||
var ErrWriter io.Writer = os.Stderr
|
||||
|
||||
// MultiError is an error that wraps multiple errors.
|
||||
type MultiError interface {
|
||||
error
|
||||
Errors() []error
|
||||
}
|
||||
|
||||
// newMultiError creates a new MultiError. Pass in one or more errors.
|
||||
func newMultiError(err ...error) MultiError {
|
||||
ret := multiError(err)
|
||||
return &ret
|
||||
}
|
||||
|
||||
type multiError []error
|
||||
|
||||
// Error implements the error interface.
|
||||
func (m *multiError) Error() string {
|
||||
errs := make([]string, len(*m))
|
||||
for i, err := range *m {
|
||||
errs[i] = err.Error()
|
||||
}
|
||||
|
||||
return strings.Join(errs, "\n")
|
||||
}
|
||||
|
||||
// Errors returns a copy of the errors slice
|
||||
func (m *multiError) Errors() []error {
|
||||
errs := make([]error, len(*m))
|
||||
copy(errs, *m)
|
||||
return errs
|
||||
}
|
||||
|
||||
type requiredFlagsErr interface {
|
||||
error
|
||||
}
|
||||
|
||||
type errRequiredFlags struct {
|
||||
missingFlags []string
|
||||
}
|
||||
|
||||
func (e *errRequiredFlags) Error() string {
|
||||
if len(e.missingFlags) == 1 {
|
||||
return fmt.Sprintf("Required flag %q not set", e.missingFlags[0])
|
||||
}
|
||||
joinedMissingFlags := strings.Join(e.missingFlags, ", ")
|
||||
return fmt.Sprintf("Required flags %q not set", joinedMissingFlags)
|
||||
}
|
||||
|
||||
type mutuallyExclusiveGroup struct {
|
||||
flag1Name string
|
||||
flag2Name string
|
||||
}
|
||||
|
||||
func (e *mutuallyExclusiveGroup) Error() string {
|
||||
return fmt.Sprintf("option %s cannot be set along with option %s", e.flag1Name, e.flag2Name)
|
||||
}
|
||||
|
||||
type mutuallyExclusiveGroupRequiredFlag struct {
|
||||
flags *MutuallyExclusiveFlags
|
||||
}
|
||||
|
||||
func (e *mutuallyExclusiveGroupRequiredFlag) Error() string {
|
||||
var missingFlags []string
|
||||
for _, grpf := range e.flags.Flags {
|
||||
var grpString []string
|
||||
for _, f := range grpf {
|
||||
grpString = append(grpString, f.Names()...)
|
||||
}
|
||||
missingFlags = append(missingFlags, strings.Join(grpString, " "))
|
||||
}
|
||||
|
||||
return fmt.Sprintf("one of these flags needs to be provided: %s", strings.Join(missingFlags, ", "))
|
||||
}
|
||||
|
||||
// ErrorFormatter is the interface that will suitably format the error output
|
||||
type ErrorFormatter interface {
|
||||
Format(s fmt.State, verb rune)
|
||||
}
|
||||
|
||||
// ExitCoder is the interface checked by `Command` for a custom exit code.
|
||||
type ExitCoder interface {
|
||||
error
|
||||
ExitCode() int
|
||||
}
|
||||
|
||||
type exitError struct {
|
||||
exitCode int
|
||||
err error
|
||||
}
|
||||
|
||||
// Exit wraps a message and exit code into an error, which by default is
|
||||
// handled with a call to os.Exit during default error handling.
|
||||
//
|
||||
// This is the simplest way to trigger a non-zero exit code for a Command without
|
||||
// having to call os.Exit manually. During testing, this behavior can be avoided
|
||||
// by overriding the ExitErrHandler function on a Command or the package-global
|
||||
// OsExiter function.
|
||||
func Exit(message any, exitCode int) ExitCoder {
|
||||
var err error
|
||||
|
||||
switch e := message.(type) {
|
||||
case ErrorFormatter:
|
||||
err = fmt.Errorf("%+v", message)
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
err = fmt.Errorf("%+v", message)
|
||||
}
|
||||
|
||||
return &exitError{
|
||||
err: err,
|
||||
exitCode: exitCode,
|
||||
}
|
||||
}
|
||||
|
||||
func (ee *exitError) Error() string {
|
||||
return ee.err.Error()
|
||||
}
|
||||
|
||||
func (ee *exitError) ExitCode() int {
|
||||
return ee.exitCode
|
||||
}
|
||||
|
||||
// HandleExitCoder handles errors implementing ExitCoder by printing their
|
||||
// message and calling OsExiter with the given exit code.
|
||||
//
|
||||
// If the given error instead implements MultiError, each error will be checked
|
||||
// for the ExitCoder interface, and OsExiter will be called with the last exit
|
||||
// code found, or exit code 1 if no ExitCoder is found.
|
||||
//
|
||||
// This function is the default error-handling behavior for a Command.
|
||||
func HandleExitCoder(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if exitErr, ok := err.(ExitCoder); ok {
|
||||
if err.Error() != "" {
|
||||
if _, ok := exitErr.(ErrorFormatter); ok {
|
||||
_, _ = fmt.Fprintf(ErrWriter, "%+v\n", err)
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(ErrWriter, err)
|
||||
}
|
||||
}
|
||||
OsExiter(exitErr.ExitCode())
|
||||
return
|
||||
}
|
||||
|
||||
if multiErr, ok := err.(MultiError); ok {
|
||||
code := handleMultiError(multiErr)
|
||||
OsExiter(code)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleMultiError(multiErr MultiError) int {
|
||||
code := 1
|
||||
for _, merr := range multiErr.Errors() {
|
||||
if multiErr2, ok := merr.(MultiError); ok {
|
||||
code = handleMultiError(multiErr2)
|
||||
} else if merr != nil {
|
||||
fmt.Fprintln(ErrWriter, merr)
|
||||
if exitErr, ok := merr.(ExitCoder); ok {
|
||||
code = exitErr.ExitCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
189
vendor/github.com/urfave/cli/v3/fish.go
generated
vendored
Normal file
189
vendor/github.com/urfave/cli/v3/fish.go
generated
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// ToFishCompletion creates a fish completion string for the `*Command`
|
||||
// The function errors if either parsing or writing of the string fails.
|
||||
func (cmd *Command) ToFishCompletion() (string, error) {
|
||||
var w bytes.Buffer
|
||||
if err := cmd.writeFishCompletionTemplate(&w); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return w.String(), nil
|
||||
}
|
||||
|
||||
type fishCommandCompletionTemplate struct {
|
||||
Command *Command
|
||||
Completions []string
|
||||
AllCommands []string
|
||||
}
|
||||
|
||||
func (cmd *Command) writeFishCompletionTemplate(w io.Writer) error {
|
||||
const name = "cli"
|
||||
t, err := template.New(name).Parse(FishCompletionTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add global flags
|
||||
completions := prepareFishFlags(cmd.Name, cmd)
|
||||
|
||||
// Add commands and their flags
|
||||
completions = append(
|
||||
completions,
|
||||
prepareFishCommands(cmd.Name, cmd)...,
|
||||
)
|
||||
|
||||
toplevelCommandNames := []string{}
|
||||
for _, child := range cmd.Commands {
|
||||
toplevelCommandNames = append(toplevelCommandNames, child.Names()...)
|
||||
}
|
||||
|
||||
return t.ExecuteTemplate(w, name, &fishCommandCompletionTemplate{
|
||||
Command: cmd,
|
||||
Completions: completions,
|
||||
AllCommands: toplevelCommandNames,
|
||||
})
|
||||
}
|
||||
|
||||
func prepareFishCommands(binary string, parent *Command) []string {
|
||||
commands := parent.Commands
|
||||
completions := []string{}
|
||||
for _, command := range commands {
|
||||
if !command.Hidden {
|
||||
var completion strings.Builder
|
||||
fmt.Fprintf(&completion,
|
||||
"complete -x -c %s -n '%s' -a '%s'",
|
||||
binary,
|
||||
fishSubcommandHelper(binary, parent, commands),
|
||||
command.Name,
|
||||
)
|
||||
|
||||
if command.Usage != "" {
|
||||
fmt.Fprintf(&completion,
|
||||
" -d '%s'",
|
||||
escapeSingleQuotes(command.Usage))
|
||||
}
|
||||
completions = append(completions, completion.String())
|
||||
}
|
||||
completions = append(
|
||||
completions,
|
||||
prepareFishFlags(binary, command)...,
|
||||
)
|
||||
|
||||
// recursively iterate subcommands
|
||||
completions = append(
|
||||
completions,
|
||||
prepareFishCommands(binary, command)...,
|
||||
)
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func prepareFishFlags(binary string, owner *Command) []string {
|
||||
flags := owner.VisibleFlags()
|
||||
completions := []string{}
|
||||
for _, f := range flags {
|
||||
completion := &strings.Builder{}
|
||||
fmt.Fprintf(completion,
|
||||
"complete -c %s -n '%s'",
|
||||
binary,
|
||||
fishFlagHelper(binary, owner),
|
||||
)
|
||||
|
||||
fishAddFileFlag(f, completion)
|
||||
|
||||
for idx, opt := range f.Names() {
|
||||
if idx == 0 {
|
||||
fmt.Fprintf(completion,
|
||||
" -l %s", strings.TrimSpace(opt),
|
||||
)
|
||||
} else {
|
||||
fmt.Fprintf(completion,
|
||||
" -s %s", strings.TrimSpace(opt),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if flag, ok := f.(DocGenerationFlag); ok {
|
||||
if flag.TakesValue() {
|
||||
completion.WriteString(" -r")
|
||||
}
|
||||
|
||||
if flag.GetUsage() != "" {
|
||||
fmt.Fprintf(completion,
|
||||
" -d '%s'",
|
||||
escapeSingleQuotes(flag.GetUsage()))
|
||||
}
|
||||
}
|
||||
|
||||
completions = append(completions, completion.String())
|
||||
}
|
||||
|
||||
return completions
|
||||
}
|
||||
|
||||
func fishAddFileFlag(flag Flag, completion *strings.Builder) {
|
||||
switch f := flag.(type) {
|
||||
case *StringFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
case *StringSliceFlag:
|
||||
if f.TakesFile {
|
||||
return
|
||||
}
|
||||
}
|
||||
completion.WriteString(" -f")
|
||||
}
|
||||
|
||||
func fishSubcommandHelper(binary string, command *Command, siblings []*Command) string {
|
||||
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", binary)
|
||||
if len(command.Lineage()) > 1 {
|
||||
var siblingNames []string
|
||||
for _, sibling := range siblings {
|
||||
siblingNames = append(siblingNames, sibling.Names()...)
|
||||
}
|
||||
ancestry := commandAncestry(command)
|
||||
fishHelper = fmt.Sprintf(
|
||||
"%s; and not __fish_seen_subcommand_from %s",
|
||||
ancestry,
|
||||
strings.Join(siblingNames, " "),
|
||||
)
|
||||
}
|
||||
return fishHelper
|
||||
}
|
||||
|
||||
func fishFlagHelper(binary string, command *Command) string {
|
||||
fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", binary)
|
||||
if len(command.Lineage()) > 1 {
|
||||
fishHelper = commandAncestry(command)
|
||||
}
|
||||
return fishHelper
|
||||
}
|
||||
|
||||
func commandAncestry(command *Command) string {
|
||||
var ancestry []string
|
||||
ancestors := command.Lineage()
|
||||
for i := len(ancestors) - 2; i >= 0; i-- {
|
||||
ancestry = append(
|
||||
ancestry,
|
||||
fmt.Sprintf(
|
||||
"__fish_seen_subcommand_from %s",
|
||||
strings.Join(ancestors[i].Names(), " "),
|
||||
),
|
||||
)
|
||||
}
|
||||
return strings.Join(ancestry, "; and ")
|
||||
}
|
||||
|
||||
func escapeSingleQuotes(input string) string {
|
||||
return strings.ReplaceAll(input, `'`, `\'`)
|
||||
}
|
||||
231
vendor/github.com/urfave/cli/v3/flag.go
generated
vendored
Normal file
231
vendor/github.com/urfave/cli/v3/flag.go
generated
vendored
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultPlaceholder = "value"
|
||||
|
||||
var (
|
||||
defaultSliceFlagSeparator = ","
|
||||
defaultMapFlagKeyValueSeparator = "="
|
||||
disableSliceFlagSeparator = false
|
||||
)
|
||||
|
||||
var (
|
||||
slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
|
||||
|
||||
commaWhitespace = regexp.MustCompile("[, ]+.*")
|
||||
)
|
||||
|
||||
// GenerateShellCompletionFlag enables shell completion
|
||||
var GenerateShellCompletionFlag Flag = &BoolFlag{
|
||||
Name: "generate-shell-completion",
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
// VersionFlag prints the version for the application
|
||||
var VersionFlag Flag = &BoolFlag{
|
||||
Name: "version",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "print the version",
|
||||
HideDefault: true,
|
||||
Local: true,
|
||||
}
|
||||
|
||||
// HelpFlag prints the help for all commands and subcommands.
|
||||
// Set to nil to disable the flag. The subcommand
|
||||
// will still be added unless HideHelp or HideHelpCommand is set to true.
|
||||
var HelpFlag Flag = &BoolFlag{
|
||||
Name: "help",
|
||||
Aliases: []string{"h"},
|
||||
Usage: "show help",
|
||||
HideDefault: true,
|
||||
Local: true,
|
||||
}
|
||||
|
||||
// FlagStringer converts a flag definition to a string. This is used by help
|
||||
// to display a flag.
|
||||
var FlagStringer FlagStringFunc = stringifyFlag
|
||||
|
||||
// Serializer is used to circumvent the limitations of flag.FlagSet.Set
|
||||
type Serializer interface {
|
||||
Serialize() string
|
||||
}
|
||||
|
||||
// FlagNamePrefixer converts a full flag name and its placeholder into the help
|
||||
// message flag prefix. This is used by the default FlagStringer.
|
||||
var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
|
||||
|
||||
// FlagEnvHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagEnvHinter FlagEnvHintFunc = withEnvHint
|
||||
|
||||
// FlagFileHinter annotates flag help message with the environment variable
|
||||
// details. This is used by the default FlagStringer.
|
||||
var FlagFileHinter FlagFileHintFunc = withFileHint
|
||||
|
||||
// FlagsByName is a slice of Flag.
|
||||
type FlagsByName []Flag
|
||||
|
||||
func (f FlagsByName) Len() int {
|
||||
return len(f)
|
||||
}
|
||||
|
||||
func (f FlagsByName) Less(i, j int) bool {
|
||||
if len(f[j].Names()) == 0 {
|
||||
return false
|
||||
} else if len(f[i].Names()) == 0 {
|
||||
return true
|
||||
}
|
||||
return lexicographicLess(f[i].Names()[0], f[j].Names()[0])
|
||||
}
|
||||
|
||||
func (f FlagsByName) Swap(i, j int) {
|
||||
f[i], f[j] = f[j], f[i]
|
||||
}
|
||||
|
||||
// ActionableFlag is an interface that wraps Flag interface and RunAction operation.
|
||||
type ActionableFlag interface {
|
||||
RunAction(context.Context, *Command) error
|
||||
}
|
||||
|
||||
// Flag is a common interface related to parsing flags in cli.
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
||||
// this interface be implemented.
|
||||
type Flag interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Retrieve the value of the Flag
|
||||
Get() any
|
||||
|
||||
// Lifecycle methods.
|
||||
// flag callback prior to parsing
|
||||
PreParse() error
|
||||
|
||||
// flag callback post parsing
|
||||
PostParse() error
|
||||
|
||||
// Apply Flag settings to the given flag set
|
||||
Set(string, string) error
|
||||
|
||||
// All possible names for this flag
|
||||
Names() []string
|
||||
|
||||
// Whether the flag has been set or not
|
||||
IsSet() bool
|
||||
}
|
||||
|
||||
// RequiredFlag is an interface that allows us to mark flags as required
|
||||
// it allows flags required flags to be backwards compatible with the Flag interface
|
||||
type RequiredFlag interface {
|
||||
// whether the flag is a required flag or not
|
||||
IsRequired() bool
|
||||
}
|
||||
|
||||
// DocGenerationFlag is an interface that allows documentation generation for the flag
|
||||
type DocGenerationFlag interface {
|
||||
// TakesValue returns true if the flag takes a value, otherwise false
|
||||
TakesValue() bool
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
GetUsage() string
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
GetValue() string
|
||||
|
||||
// GetDefaultText returns the default text for this flag
|
||||
GetDefaultText() string
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
GetEnvVars() []string
|
||||
|
||||
// IsDefaultVisible returns whether the default value should be shown in
|
||||
// help text
|
||||
IsDefaultVisible() bool
|
||||
// TypeName to detect if a flag is a string, bool, etc.
|
||||
TypeName() string
|
||||
}
|
||||
|
||||
// DocGenerationMultiValueFlag extends DocGenerationFlag for slice/map based flags.
|
||||
type DocGenerationMultiValueFlag interface {
|
||||
DocGenerationFlag
|
||||
|
||||
// IsMultiValueFlag returns true for flags that can be given multiple times.
|
||||
IsMultiValueFlag() bool
|
||||
}
|
||||
|
||||
// Countable is an interface to enable detection of flag values which support
|
||||
// repetitive flags
|
||||
type Countable interface {
|
||||
Count() int
|
||||
}
|
||||
|
||||
// VisibleFlag is an interface that allows to check if a flag is visible
|
||||
type VisibleFlag interface {
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
IsVisible() bool
|
||||
}
|
||||
|
||||
// CategorizableFlag is an interface that allows us to potentially
|
||||
// use a flag in a categorized representation.
|
||||
type CategorizableFlag interface {
|
||||
// Returns the category of the flag
|
||||
GetCategory() string
|
||||
|
||||
// Sets the category of the flag
|
||||
SetCategory(string)
|
||||
}
|
||||
|
||||
// LocalFlag is an interface to enable detection of flags which are local
|
||||
// to current command
|
||||
type LocalFlag interface {
|
||||
IsLocal() bool
|
||||
}
|
||||
|
||||
func visibleFlags(fl []Flag) []Flag {
|
||||
var visible []Flag
|
||||
for _, f := range fl {
|
||||
if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() {
|
||||
visible = append(visible, f)
|
||||
}
|
||||
}
|
||||
return visible
|
||||
}
|
||||
|
||||
func FlagNames(name string, aliases []string) []string {
|
||||
var ret []string
|
||||
|
||||
for _, part := range append([]string{name}, aliases...) {
|
||||
// v1 -> v2 migration warning zone:
|
||||
// Strip off anything after the first found comma or space, which
|
||||
// *hopefully* makes it a tiny bit more obvious that unexpected behavior is
|
||||
// caused by using the v1 form of stringly typed "Name".
|
||||
ret = append(ret, commaWhitespace.ReplaceAllString(part, ""))
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func hasFlag(flags []Flag, fl Flag) bool {
|
||||
for _, existing := range flags {
|
||||
if fl == existing {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func flagSplitMultiValues(val string) []string {
|
||||
if disableSliceFlagSeparator {
|
||||
return []string{val}
|
||||
}
|
||||
|
||||
return strings.Split(val, defaultSliceFlagSeparator)
|
||||
}
|
||||
81
vendor/github.com/urfave/cli/v3/flag_bool.go
generated
vendored
Normal file
81
vendor/github.com/urfave/cli/v3/flag_bool.go
generated
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type BoolFlag = FlagBase[bool, BoolConfig, boolValue]
|
||||
|
||||
// BoolConfig defines the configuration for bool flags
|
||||
type BoolConfig struct {
|
||||
Count *int
|
||||
}
|
||||
|
||||
// boolValue needs to implement the boolFlag internal interface in flag
|
||||
// to be able to capture bool fields and values
|
||||
//
|
||||
// type boolFlag interface {
|
||||
// Value
|
||||
// IsBoolFlag() bool
|
||||
// }
|
||||
type boolValue struct {
|
||||
destination *bool
|
||||
count *int
|
||||
}
|
||||
|
||||
func (cmd *Command) Bool(name string) bool {
|
||||
if v, ok := cmd.Value(name).(bool); ok {
|
||||
tracef("bool available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
// Create creates the bool value
|
||||
func (b boolValue) Create(val bool, p *bool, c BoolConfig) Value {
|
||||
*p = val
|
||||
if c.Count == nil {
|
||||
c.Count = new(int)
|
||||
}
|
||||
return &boolValue{
|
||||
destination: p,
|
||||
count: c.Count,
|
||||
}
|
||||
}
|
||||
|
||||
// ToString formats the bool value
|
||||
func (b boolValue) ToString(value bool) string {
|
||||
return strconv.FormatBool(value)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (b *boolValue) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
err = errors.New("parse error")
|
||||
return err
|
||||
}
|
||||
*b.destination = v
|
||||
if b.count != nil {
|
||||
*b.count = *b.count + 1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *boolValue) Get() interface{} { return *b.destination }
|
||||
|
||||
func (b *boolValue) String() string {
|
||||
return strconv.FormatBool(*b.destination)
|
||||
}
|
||||
|
||||
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||
|
||||
func (b *boolValue) Count() int {
|
||||
return *b.count
|
||||
}
|
||||
240
vendor/github.com/urfave/cli/v3/flag_bool_with_inverse.go
generated
vendored
Normal file
240
vendor/github.com/urfave/cli/v3/flag_bool_with_inverse.go
generated
vendored
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var DefaultInverseBoolPrefix = "no-"
|
||||
|
||||
type BoolWithInverseFlag struct {
|
||||
Name string `json:"name"` // name of the flag
|
||||
Category string `json:"category"` // category of the flag, if any
|
||||
DefaultText string `json:"defaultText"` // default text of the flag for usage purposes
|
||||
HideDefault bool `json:"hideDefault"` // whether to hide the default value in output
|
||||
Usage string `json:"usage"` // usage string for help output
|
||||
Sources ValueSourceChain `json:"-"` // sources to load flag value from
|
||||
Required bool `json:"required"` // whether the flag is required or not
|
||||
Hidden bool `json:"hidden"` // whether to hide the flag in help output
|
||||
Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well
|
||||
Value bool `json:"defaultValue"` // default value for this flag if not set by from any source
|
||||
Destination *bool `json:"-"` // destination pointer for value when set
|
||||
Aliases []string `json:"aliases"` // Aliases that are allowed for this flag
|
||||
TakesFile bool `json:"takesFileArg"` // whether this flag takes a file argument, mainly for shell completion purposes
|
||||
Action func(context.Context, *Command, bool) error `json:"-"` // Action callback to be called when flag is set
|
||||
OnlyOnce bool `json:"onlyOnce"` // whether this flag can be duplicated on the command line
|
||||
Validator func(bool) error `json:"-"` // custom function to validate this flag value
|
||||
ValidateDefaults bool `json:"validateDefaults"` // whether to validate defaults or not
|
||||
Config BoolConfig `json:"config"` // Additional/Custom configuration associated with this flag type
|
||||
InversePrefix string `json:"invPrefix"` // The prefix used to indicate a negative value. Default: `env` becomes `no-env`
|
||||
|
||||
// unexported fields for internal use
|
||||
count int // number of times the flag has been set
|
||||
hasBeenSet bool // whether the flag has been set from env or file
|
||||
applied bool // whether the flag has been applied to a flag set already
|
||||
value Value // value representing this flag's value
|
||||
pset bool
|
||||
nset bool
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsSet() bool {
|
||||
return bif.hasBeenSet
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Get() any {
|
||||
return bif.value.Get()
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) RunAction(ctx context.Context, cmd *Command) error {
|
||||
if bif.Action != nil {
|
||||
return bif.Action(ctx, cmd, bif.Get().(bool))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsLocal() bool {
|
||||
return bif.Local
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) inversePrefix() string {
|
||||
if bif.InversePrefix == "" {
|
||||
bif.InversePrefix = DefaultInverseBoolPrefix
|
||||
}
|
||||
|
||||
return bif.InversePrefix
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) PreParse() error {
|
||||
count := bif.Config.Count
|
||||
if count == nil {
|
||||
count = &bif.count
|
||||
}
|
||||
dest := bif.Destination
|
||||
if dest == nil {
|
||||
dest = new(bool)
|
||||
}
|
||||
*dest = bif.Value
|
||||
bif.value = &boolValue{
|
||||
destination: dest,
|
||||
count: count,
|
||||
}
|
||||
|
||||
// Validate the given default or values set from external sources as well
|
||||
if bif.Validator != nil && bif.ValidateDefaults {
|
||||
if err := bif.Validator(bif.value.Get().(bool)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
bif.applied = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) PostParse() error {
|
||||
tracef("postparse (flag=%[1]q)", bif.Name)
|
||||
|
||||
if !bif.hasBeenSet {
|
||||
if val, source, found := bif.Sources.LookupWithSource(); found {
|
||||
if val == "" {
|
||||
val = "false"
|
||||
}
|
||||
if err := bif.Set(bif.Name, val); err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
|
||||
val, bif.Value, source, bif.Name, err,
|
||||
)
|
||||
}
|
||||
|
||||
bif.hasBeenSet = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Set(name, val string) error {
|
||||
if bif.count > 0 && bif.OnlyOnce {
|
||||
return fmt.Errorf("cant duplicate this flag")
|
||||
}
|
||||
|
||||
bif.hasBeenSet = true
|
||||
|
||||
if slices.Contains(append([]string{bif.Name}, bif.Aliases...), name) {
|
||||
if bif.nset {
|
||||
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", bif.Name, bif.inversePrefix()+bif.Name)
|
||||
}
|
||||
if err := bif.value.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
bif.pset = true
|
||||
} else {
|
||||
if bif.pset {
|
||||
return fmt.Errorf("cannot set both flags `--%s` and `--%s`", bif.Name, bif.inversePrefix()+bif.Name)
|
||||
}
|
||||
if err := bif.value.Set("false"); err != nil {
|
||||
return err
|
||||
}
|
||||
bif.nset = true
|
||||
}
|
||||
|
||||
if bif.Validator != nil {
|
||||
return bif.Validator(bif.value.Get().(bool))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) Names() []string {
|
||||
names := append([]string{bif.Name}, bif.Aliases...)
|
||||
|
||||
for _, name := range names {
|
||||
names = append(names, bif.inversePrefix()+name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsRequired() bool {
|
||||
return bif.Required
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) IsVisible() bool {
|
||||
return !bif.Hidden
|
||||
}
|
||||
|
||||
// String implements the standard Stringer interface.
|
||||
//
|
||||
// Example for BoolFlag{Name: "env"}
|
||||
// --[no-]env (default: false)
|
||||
func (bif *BoolWithInverseFlag) String() string {
|
||||
out := FlagStringer(bif)
|
||||
|
||||
i := strings.Index(out, "\t")
|
||||
|
||||
prefix := "--"
|
||||
|
||||
// single character flags are prefixed with `-` instead of `--`
|
||||
if len(bif.Name) == 1 {
|
||||
prefix = "-"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s[%s]%s%s", prefix, bif.inversePrefix(), bif.Name, out[i:])
|
||||
}
|
||||
|
||||
// IsBoolFlag returns whether the flag doesnt need to accept args
|
||||
func (bif *BoolWithInverseFlag) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Count returns the number of times this flag has been invoked
|
||||
func (bif *BoolWithInverseFlag) Count() int {
|
||||
return bif.count
|
||||
}
|
||||
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (bif *BoolWithInverseFlag) GetDefaultText() string {
|
||||
if bif.Required {
|
||||
return bif.DefaultText
|
||||
}
|
||||
return boolValue{}.ToString(bif.Value)
|
||||
}
|
||||
|
||||
// GetCategory returns the category of the flag
|
||||
func (bif *BoolWithInverseFlag) GetCategory() string {
|
||||
return bif.Category
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) SetCategory(c string) {
|
||||
bif.Category = c
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (bif *BoolWithInverseFlag) GetUsage() string {
|
||||
return bif.Usage
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (bif *BoolWithInverseFlag) GetEnvVars() []string {
|
||||
return bif.Sources.EnvKeys()
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (bif *BoolWithInverseFlag) GetValue() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (bif *BoolWithInverseFlag) TakesValue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
|
||||
func (bif *BoolWithInverseFlag) IsDefaultVisible() bool {
|
||||
return !bif.HideDefault
|
||||
}
|
||||
|
||||
// TypeName is used for stringify/docs. For bool its a no-op
|
||||
func (bif *BoolWithInverseFlag) TypeName() string {
|
||||
return "bool"
|
||||
}
|
||||
47
vendor/github.com/urfave/cli/v3/flag_duration.go
generated
vendored
Normal file
47
vendor/github.com/urfave/cli/v3/flag_duration.go
generated
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DurationFlag = FlagBase[time.Duration, NoConfig, durationValue]
|
||||
|
||||
// -- time.Duration Value
|
||||
type durationValue time.Duration
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (d durationValue) Create(val time.Duration, p *time.Duration, c NoConfig) Value {
|
||||
*p = val
|
||||
return (*durationValue)(p)
|
||||
}
|
||||
|
||||
func (d durationValue) ToString(val time.Duration) string {
|
||||
return fmt.Sprintf("%v", val)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (d *durationValue) Set(s string) error {
|
||||
v, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*d = durationValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *durationValue) Get() any { return time.Duration(*d) }
|
||||
|
||||
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
|
||||
|
||||
func (cmd *Command) Duration(name string) time.Duration {
|
||||
if v, ok := cmd.Value(name).(time.Duration); ok {
|
||||
tracef("duration available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("bool NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
||||
63
vendor/github.com/urfave/cli/v3/flag_ext.go
generated
vendored
Normal file
63
vendor/github.com/urfave/cli/v3/flag_ext.go
generated
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package cli
|
||||
|
||||
import "flag"
|
||||
|
||||
type extFlag struct {
|
||||
f *flag.Flag
|
||||
}
|
||||
|
||||
func (e *extFlag) PreParse() error {
|
||||
if e.f.DefValue != "" {
|
||||
return e.Set("", e.f.DefValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *extFlag) PostParse() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *extFlag) Set(_ string, val string) error {
|
||||
return e.f.Value.Set(val)
|
||||
}
|
||||
|
||||
func (e *extFlag) Get() any {
|
||||
return e.f.Value.(flag.Getter).Get()
|
||||
}
|
||||
|
||||
func (e *extFlag) Names() []string {
|
||||
return []string{e.f.Name}
|
||||
}
|
||||
|
||||
func (e *extFlag) IsSet() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *extFlag) String() string {
|
||||
return FlagStringer(e)
|
||||
}
|
||||
|
||||
func (e *extFlag) IsVisible() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *extFlag) TakesValue() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *extFlag) GetUsage() string {
|
||||
return e.f.Usage
|
||||
}
|
||||
|
||||
func (e *extFlag) GetValue() string {
|
||||
return e.f.Value.String()
|
||||
}
|
||||
|
||||
func (e *extFlag) GetDefaultText() string {
|
||||
return e.f.DefValue
|
||||
}
|
||||
|
||||
func (e *extFlag) GetEnvVars() []string {
|
||||
return nil
|
||||
}
|
||||
75
vendor/github.com/urfave/cli/v3/flag_float.go
generated
vendored
Normal file
75
vendor/github.com/urfave/cli/v3/flag_float.go
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
FloatFlag = FlagBase[float64, NoConfig, floatValue[float64]]
|
||||
Float32Flag = FlagBase[float32, NoConfig, floatValue[float32]]
|
||||
Float64Flag = FlagBase[float64, NoConfig, floatValue[float64]]
|
||||
)
|
||||
|
||||
// -- float Value
|
||||
type floatValue[T float32 | float64] struct {
|
||||
val *T
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (f floatValue[T]) Create(val T, p *T, c NoConfig) Value {
|
||||
*p = val
|
||||
|
||||
return &floatValue[T]{val: p}
|
||||
}
|
||||
|
||||
func (f floatValue[T]) ToString(b T) string {
|
||||
return strconv.FormatFloat(float64(b), 'g', -1, int(unsafe.Sizeof(T(0))*8))
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (f *floatValue[T]) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, int(unsafe.Sizeof(T(0))*8))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*f.val = T(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *floatValue[T]) Get() any { return *f.val }
|
||||
|
||||
func (f *floatValue[T]) String() string {
|
||||
return strconv.FormatFloat(float64(*f.val), 'g', -1, int(unsafe.Sizeof(T(0))*8))
|
||||
}
|
||||
|
||||
// Float looks up the value of a local FloatFlag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Float(name string) float64 {
|
||||
return getFloat[float64](cmd, name)
|
||||
}
|
||||
|
||||
// Float32 looks up the value of a local Float32Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Float32(name string) float32 {
|
||||
return getFloat[float32](cmd, name)
|
||||
}
|
||||
|
||||
// Float64 looks up the value of a local Float64Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Float64(name string) float64 {
|
||||
return getFloat[float64](cmd, name)
|
||||
}
|
||||
|
||||
func getFloat[T float32 | float64](cmd *Command, name string) T {
|
||||
if v, ok := cmd.Value(name).(T); ok {
|
||||
tracef("float available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("float NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
||||
34
vendor/github.com/urfave/cli/v3/flag_float_slice.go
generated
vendored
Normal file
34
vendor/github.com/urfave/cli/v3/flag_float_slice.go
generated
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package cli
|
||||
|
||||
type (
|
||||
FloatSlice = SliceBase[float64, NoConfig, floatValue[float64]]
|
||||
Float32Slice = SliceBase[float32, NoConfig, floatValue[float32]]
|
||||
Float64Slice = SliceBase[float64, NoConfig, floatValue[float64]]
|
||||
FloatSliceFlag = FlagBase[[]float64, NoConfig, FloatSlice]
|
||||
Float32SliceFlag = FlagBase[[]float32, NoConfig, Float32Slice]
|
||||
Float64SliceFlag = FlagBase[[]float64, NoConfig, Float64Slice]
|
||||
)
|
||||
|
||||
var (
|
||||
NewFloatSlice = NewSliceBase[float64, NoConfig, floatValue[float64]]
|
||||
NewFloat32Slice = NewSliceBase[float32, NoConfig, floatValue[float32]]
|
||||
NewFloat64Slice = NewSliceBase[float64, NoConfig, floatValue[float64]]
|
||||
)
|
||||
|
||||
// FloatSlice looks up the value of a local FloatSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) FloatSlice(name string) []float64 {
|
||||
return getNumberSlice[float64](cmd, name)
|
||||
}
|
||||
|
||||
// Float32Slice looks up the value of a local Float32Slice, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Float32Slice(name string) []float32 {
|
||||
return getNumberSlice[float32](cmd, name)
|
||||
}
|
||||
|
||||
// Float64Slice looks up the value of a local Float64SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Float64Slice(name string) []float64 {
|
||||
return getNumberSlice[float64](cmd, name)
|
||||
}
|
||||
67
vendor/github.com/urfave/cli/v3/flag_generic.go
generated
vendored
Normal file
67
vendor/github.com/urfave/cli/v3/flag_generic.go
generated
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
package cli
|
||||
|
||||
type GenericFlag = FlagBase[Value, NoConfig, genericValue]
|
||||
|
||||
// -- Value Value
|
||||
type genericValue struct {
|
||||
val Value
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (f genericValue) Create(val Value, p *Value, c NoConfig) Value {
|
||||
*p = val
|
||||
return &genericValue{
|
||||
val: *p,
|
||||
}
|
||||
}
|
||||
|
||||
func (f genericValue) ToString(b Value) string {
|
||||
if b != nil {
|
||||
return b.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (f *genericValue) Set(s string) error {
|
||||
if f.val != nil {
|
||||
return f.val.Set(s)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *genericValue) Get() any {
|
||||
if f.val != nil {
|
||||
return f.val.Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *genericValue) String() string {
|
||||
if f.val != nil {
|
||||
return f.val.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *genericValue) IsBoolFlag() bool {
|
||||
if f.val == nil {
|
||||
return false
|
||||
}
|
||||
bf, ok := f.val.(boolFlag)
|
||||
return ok && bf.IsBoolFlag()
|
||||
}
|
||||
|
||||
// Generic looks up the value of a local GenericFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Generic(name string) Value {
|
||||
if v, ok := cmd.Value(name).(Value); ok {
|
||||
tracef("generic available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("generic NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
||||
297
vendor/github.com/urfave/cli/v3/flag_impl.go
generated
vendored
Normal file
297
vendor/github.com/urfave/cli/v3/flag_impl.go
generated
vendored
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Value represents a value as used by cli.
|
||||
// For now it implements the golang flag.Value interface
|
||||
type Value interface {
|
||||
flag.Value
|
||||
flag.Getter
|
||||
}
|
||||
|
||||
type boolFlag interface {
|
||||
IsBoolFlag() bool
|
||||
}
|
||||
|
||||
// ValueCreator is responsible for creating a flag.Value emulation
|
||||
// as well as custom formatting
|
||||
//
|
||||
// T specifies the type
|
||||
// C specifies the config for the type
|
||||
type ValueCreator[T any, C any] interface {
|
||||
Create(T, *T, C) Value
|
||||
ToString(T) string
|
||||
}
|
||||
|
||||
// NoConfig is for flags which dont need a custom configuration
|
||||
type NoConfig struct{}
|
||||
|
||||
// FlagBase [T,C,VC] is a generic flag base which can be used
|
||||
// as a boilerplate to implement the most common interfaces
|
||||
// used by urfave/cli.
|
||||
//
|
||||
// T specifies the type
|
||||
// C specifies the configuration required(if any for that flag type)
|
||||
// VC specifies the value creator which creates the flag.Value emulation
|
||||
type FlagBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
Name string `json:"name"` // name of the flag
|
||||
Category string `json:"category"` // category of the flag, if any
|
||||
DefaultText string `json:"defaultText"` // default text of the flag for usage purposes
|
||||
HideDefault bool `json:"hideDefault"` // whether to hide the default value in output
|
||||
Usage string `json:"usage"` // usage string for help output
|
||||
Sources ValueSourceChain `json:"-"` // sources to load flag value from
|
||||
Required bool `json:"required"` // whether the flag is required or not
|
||||
Hidden bool `json:"hidden"` // whether to hide the flag in help output
|
||||
Local bool `json:"local"` // whether the flag needs to be applied to subcommands as well
|
||||
Value T `json:"defaultValue"` // default value for this flag if not set by from any source
|
||||
Destination *T `json:"-"` // destination pointer for value when set
|
||||
Aliases []string `json:"aliases"` // Aliases that are allowed for this flag
|
||||
TakesFile bool `json:"takesFileArg"` // whether this flag takes a file argument, mainly for shell completion purposes
|
||||
Action func(context.Context, *Command, T) error `json:"-"` // Action callback to be called when flag is set
|
||||
Config C `json:"config"` // Additional/Custom configuration associated with this flag type
|
||||
OnlyOnce bool `json:"onlyOnce"` // whether this flag can be duplicated on the command line
|
||||
Validator func(T) error `json:"-"` // custom function to validate this flag value
|
||||
ValidateDefaults bool `json:"validateDefaults"` // whether to validate defaults or not
|
||||
|
||||
// unexported fields for internal use
|
||||
count int // number of times the flag has been set
|
||||
hasBeenSet bool // whether the flag has been set from env or file
|
||||
applied bool // whether the flag has been applied to a flag set already
|
||||
creator VC // value creator for this flag type
|
||||
value Value // value representing this flag's value
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation and an empty
|
||||
// string if the flag takes no value at all.
|
||||
func (f *FlagBase[T, C, V]) GetValue() string {
|
||||
if !f.TakesValue() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", f.Value)
|
||||
}
|
||||
|
||||
// TypeName returns the type of the flag.
|
||||
func (f *FlagBase[T, C, V]) TypeName() string {
|
||||
ty := reflect.TypeOf(f.Value)
|
||||
if ty == nil {
|
||||
return ""
|
||||
}
|
||||
// convert the typename to generic type
|
||||
convertToGenericType := func(name string) string {
|
||||
prefixMap := map[string]string{
|
||||
"float": "float",
|
||||
"int": "int",
|
||||
"uint": "uint",
|
||||
}
|
||||
for prefix, genericType := range prefixMap {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
return genericType
|
||||
}
|
||||
}
|
||||
return strings.ToLower(name)
|
||||
}
|
||||
|
||||
switch ty.Kind() {
|
||||
// if it is a Slice, then return the slice's inner type. Will nested slices be used in the future?
|
||||
case reflect.Slice:
|
||||
elemType := ty.Elem()
|
||||
return convertToGenericType(elemType.Name())
|
||||
// if it is a Map, then return the map's key and value types.
|
||||
case reflect.Map:
|
||||
keyType := ty.Key()
|
||||
valueType := ty.Elem()
|
||||
return fmt.Sprintf("%s=%s", convertToGenericType(keyType.Name()), convertToGenericType(valueType.Name()))
|
||||
default:
|
||||
return convertToGenericType(ty.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// PostParse populates the flag given the flag set and environment
|
||||
func (f *FlagBase[T, C, V]) PostParse() error {
|
||||
tracef("postparse (flag=%[1]q)", f.Name)
|
||||
|
||||
if !f.hasBeenSet {
|
||||
if val, source, found := f.Sources.LookupWithSource(); found {
|
||||
if val != "" || reflect.TypeOf(f.Value).Kind() == reflect.String {
|
||||
if err := f.Set(f.Name, val); err != nil {
|
||||
return fmt.Errorf(
|
||||
"could not parse %[1]q as %[2]T value from %[3]s for flag %[4]s: %[5]s",
|
||||
val, f.Value, source, f.Name, err,
|
||||
)
|
||||
}
|
||||
} else if val == "" && reflect.TypeOf(f.Value).Kind() == reflect.Bool {
|
||||
_ = f.Set(f.Name, "false")
|
||||
}
|
||||
|
||||
f.hasBeenSet = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlagBase[T, C, V]) PreParse() error {
|
||||
newVal := f.Value
|
||||
|
||||
if f.Destination == nil {
|
||||
f.value = f.creator.Create(newVal, new(T), f.Config)
|
||||
} else {
|
||||
f.value = f.creator.Create(newVal, f.Destination, f.Config)
|
||||
}
|
||||
|
||||
// Validate the given default or values set from external sources as well
|
||||
if f.Validator != nil && f.ValidateDefaults {
|
||||
if err := f.Validator(f.value.Get().(T)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
f.applied = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set applies given value from string
|
||||
func (f *FlagBase[T, C, V]) Set(_ string, val string) error {
|
||||
tracef("apply (flag=%[1]q)", f.Name)
|
||||
|
||||
// TODO move this phase into a separate flag initialization function
|
||||
// if flag has been applied previously then it would have already been set
|
||||
// from env or file. So no need to apply the env set again. However
|
||||
// lots of units tests prior to persistent flags assumed that the
|
||||
// flag can be applied to different flag sets multiple times while still
|
||||
// keeping the env set.
|
||||
if !f.applied || f.Local {
|
||||
if err := f.PreParse(); err != nil {
|
||||
return err
|
||||
}
|
||||
f.applied = true
|
||||
}
|
||||
|
||||
if f.count == 1 && f.OnlyOnce {
|
||||
return fmt.Errorf("cant duplicate this flag")
|
||||
}
|
||||
|
||||
f.count++
|
||||
if err := f.value.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
f.hasBeenSet = true
|
||||
if f.Validator != nil {
|
||||
if err := f.Validator(f.value.Get().(T)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlagBase[T, C, V]) Get() any {
|
||||
if f.value != nil {
|
||||
return f.value.Get()
|
||||
}
|
||||
return f.Value
|
||||
}
|
||||
|
||||
// IsDefaultVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *FlagBase[T, C, V]) IsDefaultVisible() bool {
|
||||
return !f.HideDefault
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (f *FlagBase[T, C, V]) String() string {
|
||||
return FlagStringer(f)
|
||||
}
|
||||
|
||||
// IsSet returns whether or not the flag has been set through env or file
|
||||
func (f *FlagBase[T, C, V]) IsSet() bool {
|
||||
return f.hasBeenSet
|
||||
}
|
||||
|
||||
// Names returns the names of the flag
|
||||
func (f *FlagBase[T, C, V]) Names() []string {
|
||||
return FlagNames(f.Name, f.Aliases)
|
||||
}
|
||||
|
||||
// IsRequired returns whether or not the flag is required
|
||||
func (f *FlagBase[T, C, V]) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false
|
||||
func (f *FlagBase[T, C, V]) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// GetCategory returns the category of the flag
|
||||
func (f *FlagBase[T, C, V]) GetCategory() string {
|
||||
return f.Category
|
||||
}
|
||||
|
||||
func (f *FlagBase[T, C, V]) SetCategory(c string) {
|
||||
f.Category = c
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag
|
||||
func (f *FlagBase[T, C, V]) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// GetEnvVars returns the env vars for this flag
|
||||
func (f *FlagBase[T, C, V]) GetEnvVars() []string {
|
||||
return f.Sources.EnvKeys()
|
||||
}
|
||||
|
||||
// TakesValue returns true if the flag takes a value, otherwise false
|
||||
func (f *FlagBase[T, C, V]) TakesValue() bool {
|
||||
var t T
|
||||
return reflect.TypeOf(t) == nil || reflect.TypeOf(t).Kind() != reflect.Bool
|
||||
}
|
||||
|
||||
// GetDefaultText returns the default text for this flag
|
||||
func (f *FlagBase[T, C, V]) GetDefaultText() string {
|
||||
if f.DefaultText != "" {
|
||||
return f.DefaultText
|
||||
}
|
||||
var v V
|
||||
return v.ToString(f.Value)
|
||||
}
|
||||
|
||||
// RunAction executes flag action if set
|
||||
func (f *FlagBase[T, C, V]) RunAction(ctx context.Context, cmd *Command) error {
|
||||
if f.Action != nil {
|
||||
return f.Action(ctx, cmd, f.value.Get().(T))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsMultiValueFlag returns true if the value type T can take multiple
|
||||
// values from cmd line. This is true for slice and map type flags
|
||||
func (f *FlagBase[T, C, VC]) IsMultiValueFlag() bool {
|
||||
// TBD how to specify
|
||||
if reflect.TypeOf(f.Value) == nil {
|
||||
return false
|
||||
}
|
||||
kind := reflect.TypeOf(f.Value).Kind()
|
||||
return kind == reflect.Slice || kind == reflect.Map
|
||||
}
|
||||
|
||||
// IsLocal returns false if flag needs to be persistent across subcommands
|
||||
func (f *FlagBase[T, C, VC]) IsLocal() bool {
|
||||
return f.Local
|
||||
}
|
||||
|
||||
// IsBoolFlag returns whether the flag doesnt need to accept args
|
||||
func (f *FlagBase[T, C, VC]) IsBoolFlag() bool {
|
||||
bf, ok := f.value.(boolFlag)
|
||||
return ok && bf.IsBoolFlag()
|
||||
}
|
||||
|
||||
// Count returns the number of times this flag has been invoked
|
||||
func (f *FlagBase[T, C, VC]) Count() int {
|
||||
return f.count
|
||||
}
|
||||
107
vendor/github.com/urfave/cli/v3/flag_int.go
generated
vendored
Normal file
107
vendor/github.com/urfave/cli/v3/flag_int.go
generated
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
IntFlag = FlagBase[int, IntegerConfig, intValue[int]]
|
||||
Int8Flag = FlagBase[int8, IntegerConfig, intValue[int8]]
|
||||
Int16Flag = FlagBase[int16, IntegerConfig, intValue[int16]]
|
||||
Int32Flag = FlagBase[int32, IntegerConfig, intValue[int32]]
|
||||
Int64Flag = FlagBase[int64, IntegerConfig, intValue[int64]]
|
||||
)
|
||||
|
||||
// IntegerConfig is the configuration for all integer type flags
|
||||
type IntegerConfig struct {
|
||||
Base int
|
||||
}
|
||||
|
||||
// -- int Value
|
||||
type intValue[T int | int8 | int16 | int32 | int64] struct {
|
||||
val *T
|
||||
base int
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (i intValue[T]) Create(val T, p *T, c IntegerConfig) Value {
|
||||
*p = val
|
||||
|
||||
return &intValue[T]{
|
||||
val: p,
|
||||
base: c.Base,
|
||||
}
|
||||
}
|
||||
|
||||
func (i intValue[T]) ToString(b T) string {
|
||||
if i.base == 0 {
|
||||
i.base = 10
|
||||
}
|
||||
|
||||
return strconv.FormatInt(int64(b), i.base)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (i *intValue[T]) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, i.base, int(unsafe.Sizeof(T(0))*8))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i.val = T(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *intValue[T]) Get() any { return *i.val }
|
||||
|
||||
func (i *intValue[T]) String() string {
|
||||
base := i.base
|
||||
if base == 0 {
|
||||
base = 10
|
||||
}
|
||||
|
||||
return strconv.FormatInt(int64(*i.val), base)
|
||||
}
|
||||
|
||||
// Int looks up the value of a local Int64Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Int(name string) int {
|
||||
return getInt[int](cmd, name)
|
||||
}
|
||||
|
||||
// Int8 looks up the value of a local Int8Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Int8(name string) int8 {
|
||||
return getInt[int8](cmd, name)
|
||||
}
|
||||
|
||||
// Int16 looks up the value of a local Int16Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Int16(name string) int16 {
|
||||
return getInt[int16](cmd, name)
|
||||
}
|
||||
|
||||
// Int32 looks up the value of a local Int32Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Int32(name string) int32 {
|
||||
return getInt[int32](cmd, name)
|
||||
}
|
||||
|
||||
// Int64 looks up the value of a local Int64Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Int64(name string) int64 {
|
||||
return getInt[int64](cmd, name)
|
||||
}
|
||||
|
||||
func getInt[T int | int8 | int16 | int32 | int64](cmd *Command, name string) T {
|
||||
if v, ok := cmd.Value(name).(T); ok {
|
||||
tracef("int available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("int NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
||||
52
vendor/github.com/urfave/cli/v3/flag_int_slice.go
generated
vendored
Normal file
52
vendor/github.com/urfave/cli/v3/flag_int_slice.go
generated
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package cli
|
||||
|
||||
type (
|
||||
IntSlice = SliceBase[int, IntegerConfig, intValue[int]]
|
||||
Int8Slice = SliceBase[int8, IntegerConfig, intValue[int8]]
|
||||
Int16Slice = SliceBase[int16, IntegerConfig, intValue[int16]]
|
||||
Int32Slice = SliceBase[int32, IntegerConfig, intValue[int32]]
|
||||
Int64Slice = SliceBase[int64, IntegerConfig, intValue[int64]]
|
||||
IntSliceFlag = FlagBase[[]int, IntegerConfig, IntSlice]
|
||||
Int8SliceFlag = FlagBase[[]int8, IntegerConfig, Int8Slice]
|
||||
Int16SliceFlag = FlagBase[[]int16, IntegerConfig, Int16Slice]
|
||||
Int32SliceFlag = FlagBase[[]int32, IntegerConfig, Int32Slice]
|
||||
Int64SliceFlag = FlagBase[[]int64, IntegerConfig, Int64Slice]
|
||||
)
|
||||
|
||||
var (
|
||||
NewIntSlice = NewSliceBase[int, IntegerConfig, intValue[int]]
|
||||
NewInt8Slice = NewSliceBase[int8, IntegerConfig, intValue[int8]]
|
||||
NewInt16Slice = NewSliceBase[int16, IntegerConfig, intValue[int16]]
|
||||
NewInt32Slice = NewSliceBase[int32, IntegerConfig, intValue[int32]]
|
||||
NewInt64Slice = NewSliceBase[int64, IntegerConfig, intValue[int64]]
|
||||
)
|
||||
|
||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) IntSlice(name string) []int {
|
||||
return getNumberSlice[int](cmd, name)
|
||||
}
|
||||
|
||||
// Int8Slice looks up the value of a local Int8SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Int8Slice(name string) []int8 {
|
||||
return getNumberSlice[int8](cmd, name)
|
||||
}
|
||||
|
||||
// Int16Slice looks up the value of a local Int16SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Int16Slice(name string) []int16 {
|
||||
return getNumberSlice[int16](cmd, name)
|
||||
}
|
||||
|
||||
// Int32Slice looks up the value of a local Int32SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Int32Slice(name string) []int32 {
|
||||
return getNumberSlice[int32](cmd, name)
|
||||
}
|
||||
|
||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Int64Slice(name string) []int64 {
|
||||
return getNumberSlice[int64](cmd, name)
|
||||
}
|
||||
112
vendor/github.com/urfave/cli/v3/flag_map_impl.go
generated
vendored
Normal file
112
vendor/github.com/urfave/cli/v3/flag_map_impl.go
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MapBase wraps map[string]T to satisfy flag.Value
|
||||
type MapBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
dict *map[string]T
|
||||
hasBeenSet bool
|
||||
value Value
|
||||
}
|
||||
|
||||
func (i MapBase[T, C, VC]) Create(val map[string]T, p *map[string]T, c C) Value {
|
||||
*p = map[string]T{}
|
||||
for k, v := range val {
|
||||
(*p)[k] = v
|
||||
}
|
||||
var t T
|
||||
np := new(T)
|
||||
var vc VC
|
||||
return &MapBase[T, C, VC]{
|
||||
dict: p,
|
||||
value: vc.Create(t, np, c),
|
||||
}
|
||||
}
|
||||
|
||||
// NewMapBase makes a *MapBase with default values
|
||||
func NewMapBase[T any, C any, VC ValueCreator[T, C]](defaults map[string]T) *MapBase[T, C, VC] {
|
||||
return &MapBase[T, C, VC]{
|
||||
dict: &defaults,
|
||||
}
|
||||
}
|
||||
|
||||
// Set parses the value and appends it to the list of values
|
||||
func (i *MapBase[T, C, VC]) Set(value string) error {
|
||||
if !i.hasBeenSet {
|
||||
*i.dict = map[string]T{}
|
||||
i.hasBeenSet = true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(value, slPfx) {
|
||||
// Deserializing assumes overwrite
|
||||
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.dict)
|
||||
i.hasBeenSet = true
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, item := range flagSplitMultiValues(value) {
|
||||
key, value, ok := strings.Cut(item, defaultMapFlagKeyValueSeparator)
|
||||
if !ok {
|
||||
return fmt.Errorf("item %q is missing separator %q", item, defaultMapFlagKeyValueSeparator)
|
||||
}
|
||||
if err := i.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
(*i.dict)[key] = i.value.Get().(T)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (i *MapBase[T, C, VC]) String() string {
|
||||
v := i.Value()
|
||||
var t T
|
||||
if reflect.TypeOf(t).Kind() == reflect.String {
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
return fmt.Sprintf("%T{%s}", v, i.ToString(v))
|
||||
}
|
||||
|
||||
// Serialize allows MapBase to fulfill Serializer
|
||||
func (i *MapBase[T, C, VC]) Serialize() string {
|
||||
jsonBytes, _ := json.Marshal(i.dict)
|
||||
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
|
||||
}
|
||||
|
||||
// Value returns the mapping of values set by this flag
|
||||
func (i *MapBase[T, C, VC]) Value() map[string]T {
|
||||
if i.dict == nil {
|
||||
return map[string]T{}
|
||||
}
|
||||
return *i.dict
|
||||
}
|
||||
|
||||
// Get returns the mapping of values set by this flag
|
||||
func (i *MapBase[T, C, VC]) Get() interface{} {
|
||||
return *i.dict
|
||||
}
|
||||
|
||||
func (i MapBase[T, C, VC]) ToString(t map[string]T) string {
|
||||
var defaultVals []string
|
||||
var vc VC
|
||||
for _, k := range sortedKeys(t) {
|
||||
defaultVals = append(defaultVals, k+defaultMapFlagKeyValueSeparator+vc.ToString(t[k]))
|
||||
}
|
||||
return strings.Join(defaultVals, ", ")
|
||||
}
|
||||
|
||||
func sortedKeys[T any](dict map[string]T) []string {
|
||||
keys := make([]string, 0, len(dict))
|
||||
for k := range dict {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
54
vendor/github.com/urfave/cli/v3/flag_mutex.go
generated
vendored
Normal file
54
vendor/github.com/urfave/cli/v3/flag_mutex.go
generated
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package cli
|
||||
|
||||
// MutuallyExclusiveFlags defines a mutually exclusive flag group
|
||||
// Multiple option paths can be provided out of which
|
||||
// only one can be defined on cmdline
|
||||
// So for example
|
||||
// [ --foo | [ --bar something --darth somethingelse ] ]
|
||||
type MutuallyExclusiveFlags struct {
|
||||
// Flag list
|
||||
Flags [][]Flag
|
||||
|
||||
// whether this group is required
|
||||
Required bool
|
||||
|
||||
// Category to apply to all flags within group
|
||||
Category string
|
||||
}
|
||||
|
||||
func (grp MutuallyExclusiveFlags) check(_ *Command) error {
|
||||
oneSet := false
|
||||
e := &mutuallyExclusiveGroup{}
|
||||
|
||||
for _, grpf := range grp.Flags {
|
||||
for _, f := range grpf {
|
||||
if f.IsSet() {
|
||||
if oneSet {
|
||||
e.flag2Name = f.Names()[0]
|
||||
return e
|
||||
}
|
||||
e.flag1Name = f.Names()[0]
|
||||
oneSet = true
|
||||
break
|
||||
}
|
||||
if oneSet {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !oneSet && grp.Required {
|
||||
return &mutuallyExclusiveGroupRequiredFlag{flags: &grp}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (grp MutuallyExclusiveFlags) propagateCategory() {
|
||||
for _, grpf := range grp.Flags {
|
||||
for _, f := range grpf {
|
||||
if cf, ok := f.(CategorizableFlag); ok {
|
||||
cf.SetCategory(grp.Category)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
vendor/github.com/urfave/cli/v3/flag_number_slice.go
generated
vendored
Normal file
15
vendor/github.com/urfave/cli/v3/flag_number_slice.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package cli
|
||||
|
||||
type numberType interface {
|
||||
int | int8 | int16 | int32 | int64 | float32 | float64
|
||||
}
|
||||
|
||||
func getNumberSlice[T numberType](cmd *Command, name string) []T {
|
||||
if v, ok := cmd.Value(name).([]T); ok {
|
||||
tracef("%T slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", *new(T), name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("%T slice NOT available for flag name %[1]q (cmd=%[2]q)", *new(T), name, cmd.Name)
|
||||
return nil
|
||||
}
|
||||
109
vendor/github.com/urfave/cli/v3/flag_slice_base.go
generated
vendored
Normal file
109
vendor/github.com/urfave/cli/v3/flag_slice_base.go
generated
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SliceBase wraps []T to satisfy flag.Value
|
||||
type SliceBase[T any, C any, VC ValueCreator[T, C]] struct {
|
||||
slice *[]T
|
||||
hasBeenSet bool
|
||||
value Value
|
||||
}
|
||||
|
||||
func (i SliceBase[T, C, VC]) Create(val []T, p *[]T, c C) Value {
|
||||
*p = []T{}
|
||||
*p = append(*p, val...)
|
||||
var t T
|
||||
np := new(T)
|
||||
var vc VC
|
||||
return &SliceBase[T, C, VC]{
|
||||
slice: p,
|
||||
value: vc.Create(t, np, c),
|
||||
}
|
||||
}
|
||||
|
||||
// NewSliceBase makes a *SliceBase with default values
|
||||
func NewSliceBase[T any, C any, VC ValueCreator[T, C]](defaults ...T) *SliceBase[T, C, VC] {
|
||||
return &SliceBase[T, C, VC]{
|
||||
slice: &defaults,
|
||||
}
|
||||
}
|
||||
|
||||
// Set parses the value and appends it to the list of values
|
||||
func (i *SliceBase[T, C, VC]) Set(value string) error {
|
||||
if !i.hasBeenSet {
|
||||
*i.slice = []T{}
|
||||
i.hasBeenSet = true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(value, slPfx) {
|
||||
// Deserializing assumes overwrite
|
||||
_ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice)
|
||||
i.hasBeenSet = true
|
||||
return nil
|
||||
}
|
||||
|
||||
trimSpace := true
|
||||
// hack. How do we know if we should trim spaces?
|
||||
// it makes sense only for string slice flags which have
|
||||
// an option to not trim spaces. So by default we trim spaces
|
||||
// otherwise we let the underlying value type handle it.
|
||||
var t T
|
||||
if reflect.TypeOf(t).Kind() == reflect.String {
|
||||
trimSpace = false
|
||||
}
|
||||
|
||||
for _, s := range flagSplitMultiValues(value) {
|
||||
if trimSpace {
|
||||
s = strings.TrimSpace(s)
|
||||
}
|
||||
if err := i.value.Set(s); err != nil {
|
||||
return err
|
||||
}
|
||||
*i.slice = append(*i.slice, i.value.Get().(T))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (i *SliceBase[T, C, VC]) String() string {
|
||||
v := i.Value()
|
||||
var t T
|
||||
if reflect.TypeOf(t).Kind() == reflect.String {
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
return fmt.Sprintf("%T{%s}", v, i.ToString(v))
|
||||
}
|
||||
|
||||
// Serialize allows SliceBase to fulfill Serializer
|
||||
func (i *SliceBase[T, C, VC]) Serialize() string {
|
||||
jsonBytes, _ := json.Marshal(i.slice)
|
||||
return fmt.Sprintf("%s%s", slPfx, string(jsonBytes))
|
||||
}
|
||||
|
||||
// Value returns the slice of values set by this flag
|
||||
func (i *SliceBase[T, C, VC]) Value() []T {
|
||||
if i.slice == nil {
|
||||
return nil
|
||||
}
|
||||
return *i.slice
|
||||
}
|
||||
|
||||
// Get returns the slice of values set by this flag
|
||||
func (i *SliceBase[T, C, VC]) Get() interface{} {
|
||||
return *i.slice
|
||||
}
|
||||
|
||||
func (i SliceBase[T, C, VC]) ToString(t []T) string {
|
||||
var defaultVals []string
|
||||
var v VC
|
||||
for _, s := range t {
|
||||
defaultVals = append(defaultVals, v.ToString(s))
|
||||
}
|
||||
return strings.Join(defaultVals, ", ")
|
||||
}
|
||||
66
vendor/github.com/urfave/cli/v3/flag_string.go
generated
vendored
Normal file
66
vendor/github.com/urfave/cli/v3/flag_string.go
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StringFlag = FlagBase[string, StringConfig, stringValue]
|
||||
|
||||
// StringConfig defines the configuration for string flags
|
||||
type StringConfig struct {
|
||||
// Whether to trim whitespace of parsed value
|
||||
TrimSpace bool
|
||||
}
|
||||
|
||||
// -- string Value
|
||||
type stringValue struct {
|
||||
destination *string
|
||||
trimSpace bool
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (s stringValue) Create(val string, p *string, c StringConfig) Value {
|
||||
*p = val
|
||||
return &stringValue{
|
||||
destination: p,
|
||||
trimSpace: c.TrimSpace,
|
||||
}
|
||||
}
|
||||
|
||||
func (s stringValue) ToString(val string) string {
|
||||
if val == "" {
|
||||
return val
|
||||
}
|
||||
return fmt.Sprintf("%q", val)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (s *stringValue) Set(val string) error {
|
||||
if s.trimSpace {
|
||||
val = strings.TrimSpace(val)
|
||||
}
|
||||
*s.destination = val
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringValue) Get() any { return *s.destination }
|
||||
|
||||
func (s *stringValue) String() string {
|
||||
if s.destination != nil {
|
||||
return *s.destination
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cmd *Command) String(name string) string {
|
||||
if v, ok := cmd.Value(name).(string); ok {
|
||||
tracef("string available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("string NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return ""
|
||||
}
|
||||
20
vendor/github.com/urfave/cli/v3/flag_string_map.go
generated
vendored
Normal file
20
vendor/github.com/urfave/cli/v3/flag_string_map.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package cli
|
||||
|
||||
type (
|
||||
StringMap = MapBase[string, StringConfig, stringValue]
|
||||
StringMapFlag = FlagBase[map[string]string, StringConfig, StringMap]
|
||||
)
|
||||
|
||||
var NewStringMap = NewMapBase[string, StringConfig, stringValue]
|
||||
|
||||
// StringMap looks up the value of a local StringMapFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) StringMap(name string) map[string]string {
|
||||
if v, ok := cmd.Value(name).(map[string]string); ok {
|
||||
tracef("string map available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("string map NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
||||
20
vendor/github.com/urfave/cli/v3/flag_string_slice.go
generated
vendored
Normal file
20
vendor/github.com/urfave/cli/v3/flag_string_slice.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package cli
|
||||
|
||||
type (
|
||||
StringSlice = SliceBase[string, StringConfig, stringValue]
|
||||
StringSliceFlag = FlagBase[[]string, StringConfig, StringSlice]
|
||||
)
|
||||
|
||||
var NewStringSlice = NewSliceBase[string, StringConfig, stringValue]
|
||||
|
||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) StringSlice(name string) []string {
|
||||
if v, ok := cmd.Value(name).([]string); ok {
|
||||
tracef("string slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("string slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
||||
142
vendor/github.com/urfave/cli/v3/flag_timestamp.go
generated
vendored
Normal file
142
vendor/github.com/urfave/cli/v3/flag_timestamp.go
generated
vendored
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TimestampFlag = FlagBase[time.Time, TimestampConfig, timestampValue]
|
||||
|
||||
// TimestampConfig defines the config for timestamp flags
|
||||
type TimestampConfig struct {
|
||||
Timezone *time.Location
|
||||
// Available layouts for flag value.
|
||||
//
|
||||
// Note that value for formats with missing year/date will be interpreted as current year/date respectively.
|
||||
//
|
||||
// Read more about time layouts: https://pkg.go.dev/time#pkg-constants
|
||||
Layouts []string
|
||||
}
|
||||
|
||||
// timestampValue wrap to satisfy golang's flag interface.
|
||||
type timestampValue struct {
|
||||
timestamp *time.Time
|
||||
hasBeenSet bool
|
||||
layouts []string
|
||||
location *time.Location
|
||||
}
|
||||
|
||||
var _ ValueCreator[time.Time, TimestampConfig] = timestampValue{}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (t timestampValue) Create(val time.Time, p *time.Time, c TimestampConfig) Value {
|
||||
*p = val
|
||||
return ×tampValue{
|
||||
timestamp: p,
|
||||
layouts: c.Layouts,
|
||||
location: c.Timezone,
|
||||
}
|
||||
}
|
||||
|
||||
func (t timestampValue) ToString(b time.Time) string {
|
||||
if b.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", b)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the Value interface
|
||||
|
||||
// Parses the string value to timestamp
|
||||
func (t *timestampValue) Set(value string) error {
|
||||
var timestamp time.Time
|
||||
var err error
|
||||
|
||||
if t.location == nil {
|
||||
t.location = time.UTC
|
||||
}
|
||||
|
||||
if len(t.layouts) == 0 {
|
||||
return errors.New("got nil/empty layouts slice")
|
||||
}
|
||||
|
||||
for _, layout := range t.layouts {
|
||||
var locErr error
|
||||
|
||||
timestamp, locErr = time.ParseInLocation(layout, value, t.location)
|
||||
if locErr != nil {
|
||||
if err == nil {
|
||||
err = locErr
|
||||
continue
|
||||
}
|
||||
|
||||
err = newMultiError(err, locErr)
|
||||
continue
|
||||
}
|
||||
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaultTS, _ := time.ParseInLocation(time.TimeOnly, time.TimeOnly, timestamp.Location())
|
||||
|
||||
n := time.Now().In(timestamp.Location())
|
||||
|
||||
// If format is missing date (or year only), set it explicitly to current
|
||||
if timestamp.Truncate(time.Hour*24).UnixNano() == defaultTS.Truncate(time.Hour*24).UnixNano() {
|
||||
timestamp = time.Date(
|
||||
n.Year(),
|
||||
n.Month(),
|
||||
n.Day(),
|
||||
timestamp.Hour(),
|
||||
timestamp.Minute(),
|
||||
timestamp.Second(),
|
||||
timestamp.Nanosecond(),
|
||||
timestamp.Location(),
|
||||
)
|
||||
} else if timestamp.Year() == 0 {
|
||||
timestamp = time.Date(
|
||||
n.Year(),
|
||||
timestamp.Month(),
|
||||
timestamp.Day(),
|
||||
timestamp.Hour(),
|
||||
timestamp.Minute(),
|
||||
timestamp.Second(),
|
||||
timestamp.Nanosecond(),
|
||||
timestamp.Location(),
|
||||
)
|
||||
}
|
||||
|
||||
if t.timestamp != nil {
|
||||
*t.timestamp = timestamp
|
||||
}
|
||||
t.hasBeenSet = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value (for usage defaults)
|
||||
func (t *timestampValue) String() string {
|
||||
return fmt.Sprintf("%#v", t.timestamp)
|
||||
}
|
||||
|
||||
// Get returns the flag structure
|
||||
func (t *timestampValue) Get() any {
|
||||
return *t.timestamp
|
||||
}
|
||||
|
||||
// Timestamp gets the timestamp from a flag name
|
||||
func (cmd *Command) Timestamp(name string) time.Time {
|
||||
if v, ok := cmd.Value(name).(time.Time); ok {
|
||||
tracef("time.Time available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("time.Time NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return time.Time{}
|
||||
}
|
||||
103
vendor/github.com/urfave/cli/v3/flag_uint.go
generated
vendored
Normal file
103
vendor/github.com/urfave/cli/v3/flag_uint.go
generated
vendored
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
UintFlag = FlagBase[uint, IntegerConfig, uintValue[uint]]
|
||||
Uint8Flag = FlagBase[uint8, IntegerConfig, uintValue[uint8]]
|
||||
Uint16Flag = FlagBase[uint16, IntegerConfig, uintValue[uint16]]
|
||||
Uint32Flag = FlagBase[uint32, IntegerConfig, uintValue[uint32]]
|
||||
Uint64Flag = FlagBase[uint64, IntegerConfig, uintValue[uint64]]
|
||||
)
|
||||
|
||||
// -- uint Value
|
||||
type uintValue[T uint | uint8 | uint16 | uint32 | uint64] struct {
|
||||
val *T
|
||||
base int
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the ValueCreator interface
|
||||
|
||||
func (i uintValue[T]) Create(val T, p *T, c IntegerConfig) Value {
|
||||
*p = val
|
||||
|
||||
return &uintValue[T]{
|
||||
val: p,
|
||||
base: c.Base,
|
||||
}
|
||||
}
|
||||
|
||||
func (i uintValue[T]) ToString(b T) string {
|
||||
base := i.base
|
||||
if base == 0 {
|
||||
base = 10
|
||||
}
|
||||
|
||||
return strconv.FormatUint(uint64(b), base)
|
||||
}
|
||||
|
||||
// Below functions are to satisfy the flag.Value interface
|
||||
|
||||
func (i *uintValue[T]) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, i.base, int(unsafe.Sizeof(T(0))*8))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*i.val = T(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *uintValue[T]) Get() any { return *i.val }
|
||||
|
||||
func (i *uintValue[T]) String() string {
|
||||
base := i.base
|
||||
if base == 0 {
|
||||
base = 10
|
||||
}
|
||||
|
||||
return strconv.FormatUint(uint64(*i.val), base)
|
||||
}
|
||||
|
||||
// Uint looks up the value of a local Uint64Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Uint(name string) uint {
|
||||
return getUint[uint](cmd, name)
|
||||
}
|
||||
|
||||
// Uint8 looks up the value of a local Uint8Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Uint8(name string) uint8 {
|
||||
return getUint[uint8](cmd, name)
|
||||
}
|
||||
|
||||
// Uint16 looks up the value of a local Uint16Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Uint16(name string) uint16 {
|
||||
return getUint[uint16](cmd, name)
|
||||
}
|
||||
|
||||
// Uint32 looks up the value of a local Uint32Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Uint32(name string) uint32 {
|
||||
return getUint[uint32](cmd, name)
|
||||
}
|
||||
|
||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
||||
// 0 if not found
|
||||
func (cmd *Command) Uint64(name string) uint64 {
|
||||
return getUint[uint64](cmd, name)
|
||||
}
|
||||
|
||||
func getUint[T uint | uint8 | uint16 | uint32 | uint64](cmd *Command, name string) T {
|
||||
if v, ok := cmd.Value(name).(T); ok {
|
||||
tracef("uint available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("uint NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return 0
|
||||
}
|
||||
63
vendor/github.com/urfave/cli/v3/flag_uint_slice.go
generated
vendored
Normal file
63
vendor/github.com/urfave/cli/v3/flag_uint_slice.go
generated
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
package cli
|
||||
|
||||
type (
|
||||
UintSlice = SliceBase[uint, IntegerConfig, uintValue[uint]]
|
||||
Uint8Slice = SliceBase[uint8, IntegerConfig, uintValue[uint8]]
|
||||
Uint16Slice = SliceBase[uint16, IntegerConfig, uintValue[uint16]]
|
||||
Uint32Slice = SliceBase[uint32, IntegerConfig, uintValue[uint32]]
|
||||
Uint64Slice = SliceBase[uint64, IntegerConfig, uintValue[uint64]]
|
||||
UintSliceFlag = FlagBase[[]uint, IntegerConfig, UintSlice]
|
||||
Uint8SliceFlag = FlagBase[[]uint8, IntegerConfig, Uint8Slice]
|
||||
Uint16SliceFlag = FlagBase[[]uint16, IntegerConfig, Uint16Slice]
|
||||
Uint32SliceFlag = FlagBase[[]uint32, IntegerConfig, Uint32Slice]
|
||||
Uint64SliceFlag = FlagBase[[]uint64, IntegerConfig, Uint64Slice]
|
||||
)
|
||||
|
||||
var (
|
||||
NewUintSlice = NewSliceBase[uint, IntegerConfig, uintValue[uint]]
|
||||
NewUint8Slice = NewSliceBase[uint8, IntegerConfig, uintValue[uint8]]
|
||||
NewUint16Slice = NewSliceBase[uint16, IntegerConfig, uintValue[uint16]]
|
||||
NewUint32Slice = NewSliceBase[uint32, IntegerConfig, uintValue[uint32]]
|
||||
NewUint64Slice = NewSliceBase[uint64, IntegerConfig, uintValue[uint64]]
|
||||
)
|
||||
|
||||
// UintSlice looks up the value of a local UintSliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) UintSlice(name string) []uint {
|
||||
return getUintSlice[uint](cmd, name)
|
||||
}
|
||||
|
||||
// Uint8Slice looks up the value of a local Uint8SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Uint8Slice(name string) []uint8 {
|
||||
return getUintSlice[uint8](cmd, name)
|
||||
}
|
||||
|
||||
// Uint16Slice looks up the value of a local Uint16SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Uint16Slice(name string) []uint16 {
|
||||
return getUintSlice[uint16](cmd, name)
|
||||
}
|
||||
|
||||
// Uint32Slice looks up the value of a local Uint32SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Uint32Slice(name string) []uint32 {
|
||||
return getUintSlice[uint32](cmd, name)
|
||||
}
|
||||
|
||||
// Uint64Slice looks up the value of a local Uint64SliceFlag, returns
|
||||
// nil if not found
|
||||
func (cmd *Command) Uint64Slice(name string) []uint64 {
|
||||
return getUintSlice[uint64](cmd, name)
|
||||
}
|
||||
|
||||
func getUintSlice[T uint | uint8 | uint16 | uint32 | uint64](cmd *Command, name string) []T {
|
||||
if v, ok := cmd.Value(name).([]T); ok {
|
||||
tracef("uint slice available for flag name %[1]q with value=%[2]v (cmd=%[3]q)", name, v, cmd.Name)
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
tracef("uint slice NOT available for flag name %[1]q (cmd=%[2]q)", name, cmd.Name)
|
||||
return nil
|
||||
}
|
||||
53
vendor/github.com/urfave/cli/v3/funcs.go
generated
vendored
Normal file
53
vendor/github.com/urfave/cli/v3/funcs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package cli
|
||||
|
||||
import "context"
|
||||
|
||||
// ShellCompleteFunc is an action to execute when the shell completion flag is set
|
||||
type ShellCompleteFunc func(context.Context, *Command)
|
||||
|
||||
// BeforeFunc is an action that executes prior to any subcommands being run once
|
||||
// the context is ready. If a non-nil error is returned, no subcommands are
|
||||
// run.
|
||||
type BeforeFunc func(context.Context, *Command) (context.Context, error)
|
||||
|
||||
// AfterFunc is an action that executes after any subcommands are run and have
|
||||
// finished. The AfterFunc is run even if Action() panics.
|
||||
type AfterFunc func(context.Context, *Command) error
|
||||
|
||||
// ActionFunc is the action to execute when no subcommands are specified
|
||||
type ActionFunc func(context.Context, *Command) error
|
||||
|
||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
||||
type CommandNotFoundFunc func(context.Context, *Command, string)
|
||||
|
||||
// ConfigureShellCompletionCommand is a function to configure a shell completion command
|
||||
type ConfigureShellCompletionCommand func(*Command)
|
||||
|
||||
// OnUsageErrorFunc is executed if a usage error occurs. This is useful for displaying
|
||||
// customized usage error messages. This function is able to replace the
|
||||
// original error messages. If this function is not set, the "Incorrect usage"
|
||||
// is displayed and the execution is interrupted.
|
||||
type OnUsageErrorFunc func(ctx context.Context, cmd *Command, err error, isSubcommand bool) error
|
||||
|
||||
// InvalidFlagAccessFunc is executed when an invalid flag is accessed from the context.
|
||||
type InvalidFlagAccessFunc func(context.Context, *Command, string)
|
||||
|
||||
// ExitErrHandlerFunc is executed if provided in order to handle exitError values
|
||||
// returned by Actions and Before/After functions.
|
||||
type ExitErrHandlerFunc func(context.Context, *Command, error)
|
||||
|
||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
||||
// expected to be a single line.
|
||||
type FlagStringFunc func(Flag) string
|
||||
|
||||
// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
|
||||
// text for a flag's full name.
|
||||
type FlagNamePrefixFunc func(fullName []string, placeholder string) string
|
||||
|
||||
// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
|
||||
// with the environment variable details.
|
||||
type FlagEnvHintFunc func(envVars []string, str string) string
|
||||
|
||||
// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
|
||||
// with the file path details.
|
||||
type FlagFileHintFunc func(filePath, str string) string
|
||||
1482
vendor/github.com/urfave/cli/v3/godoc-current.txt
generated
vendored
Normal file
1482
vendor/github.com/urfave/cli/v3/godoc-current.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
620
vendor/github.com/urfave/cli/v3/help.go
generated
vendored
Normal file
620
vendor/github.com/urfave/cli/v3/help.go
generated
vendored
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
helpName = "help"
|
||||
helpAlias = "h"
|
||||
)
|
||||
|
||||
// HelpPrinterFunc prints help for the Command.
|
||||
type HelpPrinterFunc func(w io.Writer, templ string, data any)
|
||||
|
||||
// Prints help for the Command with custom template function.
|
||||
type HelpPrinterCustomFunc func(w io.Writer, templ string, data any, customFunc map[string]any)
|
||||
|
||||
// HelpPrinter is a function that writes the help output. If not set explicitly,
|
||||
// this calls HelpPrinterCustom using only the default template functions.
|
||||
//
|
||||
// If custom logic for printing help is required, this function can be
|
||||
// overridden. If the ExtraInfo field is defined on a Command, this function
|
||||
// should not be modified, as HelpPrinterCustom will be used directly in order
|
||||
// to capture the extra information.
|
||||
var HelpPrinter HelpPrinterFunc = DefaultPrintHelp
|
||||
|
||||
// HelpPrinterCustom is a function that writes the help output. It is used as
|
||||
// the default implementation of HelpPrinter, and may be called directly if
|
||||
// the ExtraInfo field is set on a Command.
|
||||
//
|
||||
// In the default implementation, if the customFuncs argument contains a
|
||||
// "wrapAt" key, which is a function which takes no arguments and returns
|
||||
// an int, this int value will be used to produce a "wrap" function used
|
||||
// by the default template to wrap long lines.
|
||||
var HelpPrinterCustom HelpPrinterCustomFunc = DefaultPrintHelpCustom
|
||||
|
||||
// VersionPrinter prints the version for the root Command.
|
||||
var VersionPrinter = DefaultPrintVersion
|
||||
|
||||
// ShowRootCommandHelp is an action that displays help for the root command.
|
||||
var ShowRootCommandHelp = DefaultShowRootCommandHelp
|
||||
|
||||
// ShowAppHelp is a backward-compatible name for ShowRootCommandHelp.
|
||||
var ShowAppHelp = ShowRootCommandHelp
|
||||
|
||||
// ShowCommandHelp prints help for the given command
|
||||
var ShowCommandHelp = DefaultShowCommandHelp
|
||||
|
||||
// ShowSubcommandHelp prints help for the given subcommand
|
||||
var ShowSubcommandHelp = DefaultShowSubcommandHelp
|
||||
|
||||
func buildHelpCommand(withAction bool) *Command {
|
||||
cmd := &Command{
|
||||
Name: helpName,
|
||||
Aliases: []string{helpAlias},
|
||||
Usage: "Shows a list of commands or help for one command",
|
||||
ArgsUsage: "[command]",
|
||||
HideHelp: true,
|
||||
}
|
||||
|
||||
if withAction {
|
||||
cmd.Action = helpCommandAction
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func helpCommandAction(ctx context.Context, cmd *Command) error {
|
||||
args := cmd.Args()
|
||||
firstArg := args.First()
|
||||
|
||||
tracef("doing help for cmd %[1]q with args %[2]q", cmd, args)
|
||||
|
||||
// This action can be triggered by a "default" action of a command
|
||||
// or via cmd.Run when cmd == helpCmd. So we have following possibilities
|
||||
//
|
||||
// 1 $ app
|
||||
// 2 $ app help
|
||||
// 3 $ app foo
|
||||
// 4 $ app help foo
|
||||
// 5 $ app foo help
|
||||
|
||||
// Case 4. when executing a help command set the context to parent
|
||||
// to allow resolution of subsequent args. This will transform
|
||||
// $ app help foo
|
||||
// to
|
||||
// $ app foo
|
||||
// which will then be handled as case 3
|
||||
if cmd.parent != nil && (cmd.HasName(helpName) || cmd.HasName(helpAlias)) {
|
||||
tracef("setting cmd to cmd.parent")
|
||||
cmd = cmd.parent
|
||||
}
|
||||
|
||||
// Case 4. $ app help foo
|
||||
// foo is the command for which help needs to be shown
|
||||
if firstArg != "" {
|
||||
/* if firstArg == "--" {
|
||||
return nil
|
||||
}*/
|
||||
tracef("returning ShowCommandHelp with %[1]q", firstArg)
|
||||
return ShowCommandHelp(ctx, cmd, firstArg)
|
||||
}
|
||||
|
||||
// Case 1 & 2
|
||||
// Special case when running help on main app itself as opposed to individual
|
||||
// commands/subcommands
|
||||
if cmd.parent == nil {
|
||||
tracef("returning ShowRootCommandHelp")
|
||||
_ = ShowRootCommandHelp(cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Case 3, 5
|
||||
if (len(cmd.Commands) == 1 && !cmd.HideHelp) ||
|
||||
(len(cmd.Commands) == 0 && cmd.HideHelp) {
|
||||
|
||||
tmpl := cmd.CustomHelpTemplate
|
||||
if tmpl == "" {
|
||||
tmpl = CommandHelpTemplate
|
||||
}
|
||||
|
||||
tracef("running HelpPrinter with command %[1]q", cmd.Name)
|
||||
HelpPrinter(cmd.Root().Writer, tmpl, cmd)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
tracef("running ShowSubcommandHelp")
|
||||
return ShowSubcommandHelp(cmd)
|
||||
}
|
||||
|
||||
// ShowRootCommandHelpAndExit prints the list of subcommands and exits with exit code.
|
||||
func ShowRootCommandHelpAndExit(cmd *Command, exitCode int) {
|
||||
_ = ShowRootCommandHelp(cmd)
|
||||
OsExiter(exitCode)
|
||||
}
|
||||
|
||||
// ShowAppHelpAndExit is a backward-compatible name for ShowRootCommandHelp.
|
||||
var ShowAppHelpAndExit = ShowRootCommandHelpAndExit
|
||||
|
||||
// DefaultShowRootCommandHelp is the default implementation of ShowRootCommandHelp.
|
||||
func DefaultShowRootCommandHelp(cmd *Command) error {
|
||||
tmpl := cmd.CustomRootCommandHelpTemplate
|
||||
if tmpl == "" {
|
||||
tracef("using RootCommandHelpTemplate")
|
||||
tmpl = RootCommandHelpTemplate
|
||||
}
|
||||
|
||||
if cmd.ExtraInfo == nil {
|
||||
HelpPrinter(cmd.Root().Writer, tmpl, cmd.Root())
|
||||
return nil
|
||||
}
|
||||
|
||||
tracef("setting ExtraInfo in customAppData")
|
||||
customAppData := func() map[string]any {
|
||||
return map[string]any{
|
||||
"ExtraInfo": cmd.ExtraInfo,
|
||||
}
|
||||
}
|
||||
HelpPrinterCustom(cmd.Root().Writer, tmpl, cmd.Root(), customAppData())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultRootCommandComplete prints the list of subcommands as the default completion method.
|
||||
func DefaultRootCommandComplete(ctx context.Context, cmd *Command) {
|
||||
DefaultCompleteWithFlags(ctx, cmd)
|
||||
}
|
||||
|
||||
// DefaultAppComplete is a backward-compatible name for DefaultRootCommandComplete.
|
||||
var DefaultAppComplete = DefaultRootCommandComplete
|
||||
|
||||
func printCommandSuggestions(commands []*Command, writer io.Writer) {
|
||||
for _, command := range commands {
|
||||
if command.Hidden {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
|
||||
_, _ = fmt.Fprintf(writer, "%s:%s\n", command.Name, command.Usage)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(writer, "%s\n", command.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cliArgContains(flagName string, args []string) bool {
|
||||
for _, name := range strings.Split(flagName, ",") {
|
||||
name = strings.TrimSpace(name)
|
||||
count := utf8.RuneCountInString(name)
|
||||
if count > 2 {
|
||||
count = 2
|
||||
}
|
||||
flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
|
||||
for _, a := range args {
|
||||
if a == flag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) {
|
||||
// Trim to handle both "-short" and "--long" flags.
|
||||
cur := strings.TrimLeft(lastArg, "-")
|
||||
for _, flag := range flags {
|
||||
if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
usage := ""
|
||||
if docFlag, ok := flag.(DocGenerationFlag); ok {
|
||||
usage = docFlag.GetUsage()
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(flag.Names()[0])
|
||||
// this will get total count utf8 letters in flag name
|
||||
count := utf8.RuneCountInString(name)
|
||||
if count > 2 {
|
||||
count = 2 // reuse this count to generate single - or -- in flag completion
|
||||
}
|
||||
// if flag name has more than one utf8 letter and last argument in cli has -- prefix then
|
||||
// skip flag completion for short flags example -v or -x
|
||||
if strings.HasPrefix(lastArg, "--") && count == 1 {
|
||||
continue
|
||||
}
|
||||
// match if last argument matches this flag and it is not repeated
|
||||
if strings.HasPrefix(name, cur) && cur != name /* && !cliArgContains(name, os.Args)*/ {
|
||||
flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name)
|
||||
if usage != "" && strings.HasSuffix(os.Getenv("SHELL"), "zsh") {
|
||||
flagCompletion = fmt.Sprintf("%s:%s", flagCompletion, usage)
|
||||
}
|
||||
fmt.Fprintln(writer, flagCompletion)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultCompleteWithFlags(ctx context.Context, cmd *Command) {
|
||||
args := os.Args
|
||||
if cmd != nil && cmd.parent != nil {
|
||||
args = cmd.Args().Slice()
|
||||
tracef("running default complete with flags[%v] on command %[2]q", args, cmd.Name)
|
||||
} else {
|
||||
tracef("running default complete with os.Args flags[%v]", args)
|
||||
}
|
||||
argsLen := len(args)
|
||||
lastArg := ""
|
||||
// parent command will have --generate-shell-completion so we need
|
||||
// to account for that
|
||||
if argsLen > 1 {
|
||||
lastArg = args[argsLen-2]
|
||||
} else if argsLen > 0 {
|
||||
lastArg = args[argsLen-1]
|
||||
}
|
||||
|
||||
if lastArg == "--" {
|
||||
tracef("No completions due to termination")
|
||||
return
|
||||
}
|
||||
|
||||
if lastArg == completionFlag {
|
||||
lastArg = ""
|
||||
}
|
||||
|
||||
if strings.HasPrefix(lastArg, "-") {
|
||||
tracef("printing flag suggestion for flag[%v] on command %[1]q", lastArg, cmd.Name)
|
||||
printFlagSuggestions(lastArg, cmd.Flags, cmd.Root().Writer)
|
||||
return
|
||||
}
|
||||
|
||||
if cmd != nil {
|
||||
tracef("printing command suggestions on command %[1]q", cmd.Name)
|
||||
printCommandSuggestions(cmd.Commands, cmd.Root().Writer)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ShowCommandHelpAndExit exits with code after showing help via ShowCommandHelp.
|
||||
func ShowCommandHelpAndExit(ctx context.Context, cmd *Command, command string, code int) {
|
||||
_ = ShowCommandHelp(ctx, cmd, command)
|
||||
OsExiter(code)
|
||||
}
|
||||
|
||||
// DefaultShowCommandHelp is the default implementation of ShowCommandHelp.
|
||||
func DefaultShowCommandHelp(ctx context.Context, cmd *Command, commandName string) error {
|
||||
for _, subCmd := range cmd.Commands {
|
||||
if !subCmd.HasName(commandName) {
|
||||
continue
|
||||
}
|
||||
|
||||
tmpl := subCmd.CustomHelpTemplate
|
||||
if tmpl == "" {
|
||||
if len(subCmd.Commands) == 0 {
|
||||
tracef("using CommandHelpTemplate")
|
||||
tmpl = CommandHelpTemplate
|
||||
} else {
|
||||
tracef("using SubcommandHelpTemplate")
|
||||
tmpl = SubcommandHelpTemplate
|
||||
}
|
||||
}
|
||||
|
||||
tracef("running HelpPrinter")
|
||||
HelpPrinter(cmd.Root().Writer, tmpl, subCmd)
|
||||
|
||||
tracef("returning nil after printing help")
|
||||
return nil
|
||||
}
|
||||
|
||||
tracef("no matching command found")
|
||||
|
||||
if cmd.CommandNotFound == nil {
|
||||
errMsg := fmt.Sprintf("No help topic for '%v'", commandName)
|
||||
|
||||
if cmd.Suggest {
|
||||
if suggestion := SuggestCommand(cmd.Commands, commandName); suggestion != "" {
|
||||
errMsg += ". " + suggestion
|
||||
}
|
||||
}
|
||||
|
||||
tracef("exiting 3 with errMsg %[1]q", errMsg)
|
||||
return Exit(errMsg, 3)
|
||||
}
|
||||
|
||||
tracef("running CommandNotFound func for %[1]q", commandName)
|
||||
cmd.CommandNotFound(ctx, cmd, commandName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowSubcommandHelpAndExit prints help for the given subcommand via ShowSubcommandHelp and exits with exit code.
|
||||
func ShowSubcommandHelpAndExit(cmd *Command, exitCode int) {
|
||||
_ = ShowSubcommandHelp(cmd)
|
||||
OsExiter(exitCode)
|
||||
}
|
||||
|
||||
// DefaultShowSubcommandHelp is the default implementation of ShowSubcommandHelp.
|
||||
func DefaultShowSubcommandHelp(cmd *Command) error {
|
||||
HelpPrinter(cmd.Root().Writer, SubcommandHelpTemplate, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowVersion prints the version number of the root Command.
|
||||
func ShowVersion(cmd *Command) {
|
||||
tracef("showing version via VersionPrinter (cmd=%[1]q)", cmd.Name)
|
||||
VersionPrinter(cmd)
|
||||
}
|
||||
|
||||
// DefaultPrintVersion is the default implementation of VersionPrinter.
|
||||
func DefaultPrintVersion(cmd *Command) {
|
||||
_, _ = fmt.Fprintf(cmd.Root().Writer, "%v version %v\n", cmd.Name, cmd.Version)
|
||||
}
|
||||
|
||||
func handleTemplateError(err error) {
|
||||
if err != nil {
|
||||
tracef("error encountered during template parse: %[1]v", err)
|
||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
||||
// we can do to recover.
|
||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" {
|
||||
_, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultPrintHelpCustom is the default implementation of HelpPrinterCustom.
|
||||
//
|
||||
// The customFuncs map will be combined with a default template.FuncMap to
|
||||
// allow using arbitrary functions in template rendering.
|
||||
func DefaultPrintHelpCustom(out io.Writer, templ string, data any, customFuncs map[string]any) {
|
||||
const maxLineLength = 10000
|
||||
|
||||
tracef("building default funcMap")
|
||||
funcMap := template.FuncMap{
|
||||
"join": strings.Join,
|
||||
"subtract": subtract,
|
||||
"indent": indent,
|
||||
"nindent": nindent,
|
||||
"trim": strings.TrimSpace,
|
||||
"wrap": func(input string, offset int) string { return wrap(input, offset, maxLineLength) },
|
||||
"offset": offset,
|
||||
"offsetCommands": offsetCommands,
|
||||
}
|
||||
|
||||
if wa, ok := customFuncs["wrapAt"]; ok {
|
||||
if wrapAtFunc, ok := wa.(func() int); ok {
|
||||
wrapAt := wrapAtFunc()
|
||||
customFuncs["wrap"] = func(input string, offset int) string {
|
||||
return wrap(input, offset, wrapAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range customFuncs {
|
||||
funcMap[key] = value
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0)
|
||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ))
|
||||
|
||||
if _, err := t.New("helpNameTemplate").Parse(helpNameTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("argsTemplate").Parse(argsTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("usageTemplate").Parse(usageTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("descriptionTemplate").Parse(descriptionTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleCommandTemplate").Parse(visibleCommandTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("copyrightTemplate").Parse(copyrightTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("versionTemplate").Parse(versionTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleFlagCategoryTemplate").Parse(visibleFlagCategoryTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleFlagTemplate").Parse(visibleFlagTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visiblePersistentFlagTemplate").Parse(visiblePersistentFlagTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleGlobalFlagCategoryTemplate").Parse(strings.ReplaceAll(visibleFlagCategoryTemplate, "OPTIONS", "GLOBAL OPTIONS")); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("authorsTemplate").Parse(authorsTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
if _, err := t.New("visibleCommandCategoryTemplate").Parse(visibleCommandCategoryTemplate); err != nil {
|
||||
handleTemplateError(err)
|
||||
}
|
||||
|
||||
tracef("executing template")
|
||||
handleTemplateError(t.Execute(w, data))
|
||||
|
||||
_ = w.Flush()
|
||||
}
|
||||
|
||||
// DefaultPrintHelp is the default implementation of HelpPrinter.
|
||||
func DefaultPrintHelp(out io.Writer, templ string, data any) {
|
||||
HelpPrinterCustom(out, templ, data, nil)
|
||||
}
|
||||
|
||||
func checkVersion(cmd *Command) bool {
|
||||
found := false
|
||||
for _, name := range VersionFlag.Names() {
|
||||
if cmd.Bool(name) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
func checkShellCompleteFlag(c *Command, arguments []string) (bool, []string) {
|
||||
if (c.parent == nil && !c.EnableShellCompletion) || (c.parent != nil && !c.Root().shellCompletion) {
|
||||
return false, arguments
|
||||
}
|
||||
|
||||
pos := len(arguments) - 1
|
||||
lastArg := arguments[pos]
|
||||
|
||||
if lastArg != completionFlag {
|
||||
return false, arguments
|
||||
}
|
||||
|
||||
for _, arg := range arguments {
|
||||
// If arguments include "--", shell completion is disabled
|
||||
// because after "--" only positional arguments are accepted.
|
||||
// https://unix.stackexchange.com/a/11382
|
||||
if arg == "--" {
|
||||
return false, arguments[:pos]
|
||||
}
|
||||
}
|
||||
|
||||
return true, arguments[:pos]
|
||||
}
|
||||
|
||||
func checkCompletions(ctx context.Context, cmd *Command) bool {
|
||||
tracef("checking completions on command %[1]q", cmd.Name)
|
||||
|
||||
if !cmd.Root().shellCompletion {
|
||||
tracef("completion not enabled skipping %[1]q", cmd.Name)
|
||||
return false
|
||||
}
|
||||
|
||||
if argsArguments := cmd.Args(); argsArguments.Present() {
|
||||
name := argsArguments.First()
|
||||
if cmd := cmd.Command(name); cmd != nil {
|
||||
// let the command handle the completion
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
tracef("no subcommand found for completion %[1]q", cmd.Name)
|
||||
|
||||
if cmd.ShellComplete != nil {
|
||||
tracef("running shell completion func for command %[1]q", cmd.Name)
|
||||
cmd.ShellComplete(ctx, cmd)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func subtract(a, b int) int {
|
||||
return a - b
|
||||
}
|
||||
|
||||
func indent(spaces int, v string) string {
|
||||
pad := strings.Repeat(" ", spaces)
|
||||
return pad + strings.ReplaceAll(v, "\n", "\n"+pad)
|
||||
}
|
||||
|
||||
func nindent(spaces int, v string) string {
|
||||
return "\n" + indent(spaces, v)
|
||||
}
|
||||
|
||||
func wrap(input string, offset int, wrapAt int) string {
|
||||
var ss []string
|
||||
|
||||
lines := strings.Split(input, "\n")
|
||||
|
||||
padding := strings.Repeat(" ", offset)
|
||||
|
||||
for i, line := range lines {
|
||||
if line == "" {
|
||||
ss = append(ss, line)
|
||||
} else {
|
||||
wrapped := wrapLine(line, offset, wrapAt, padding)
|
||||
if i == 0 {
|
||||
ss = append(ss, wrapped)
|
||||
} else {
|
||||
ss = append(ss, padding+wrapped)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(ss, "\n")
|
||||
}
|
||||
|
||||
func wrapLine(input string, offset int, wrapAt int, padding string) string {
|
||||
if wrapAt <= offset || len(input) <= wrapAt-offset {
|
||||
return input
|
||||
}
|
||||
|
||||
lineWidth := wrapAt - offset
|
||||
words := strings.Fields(input)
|
||||
if len(words) == 0 {
|
||||
return input
|
||||
}
|
||||
|
||||
wrapped := words[0]
|
||||
spaceLeft := lineWidth - len(wrapped)
|
||||
for _, word := range words[1:] {
|
||||
if len(word)+1 > spaceLeft {
|
||||
wrapped += "\n" + padding + word
|
||||
spaceLeft = lineWidth - len(word)
|
||||
} else {
|
||||
wrapped += " " + word
|
||||
spaceLeft -= 1 + len(word)
|
||||
}
|
||||
}
|
||||
|
||||
return wrapped
|
||||
}
|
||||
|
||||
func offset(input string, fixed int) int {
|
||||
return len(input) + fixed
|
||||
}
|
||||
|
||||
// this function tries to find the max width of the names column
|
||||
// so say we have the following rows for help
|
||||
//
|
||||
// foo1, foo2, foo3 some string here
|
||||
// bar1, b2 some other string here
|
||||
//
|
||||
// We want to offset the 2nd row usage by some amount so that everything
|
||||
// is aligned
|
||||
//
|
||||
// foo1, foo2, foo3 some string here
|
||||
// bar1, b2 some other string here
|
||||
//
|
||||
// to find that offset we find the length of all the rows and use the max
|
||||
// to calculate the offset
|
||||
func offsetCommands(cmds []*Command, fixed int) int {
|
||||
max := 0
|
||||
for _, cmd := range cmds {
|
||||
s := strings.Join(cmd.Names(), ", ")
|
||||
if len(s) > max {
|
||||
max = len(s)
|
||||
}
|
||||
}
|
||||
return max + fixed
|
||||
}
|
||||
5
vendor/github.com/urfave/cli/v3/mkdocs-reqs.txt
generated
vendored
Normal file
5
vendor/github.com/urfave/cli/v3/mkdocs-reqs.txt
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mkdocs-git-revision-date-localized-plugin~=1.2
|
||||
mkdocs-material~=9.5
|
||||
mkdocs~=1.6
|
||||
mkdocs-redirects~=1.2
|
||||
pygments~=2.18
|
||||
139
vendor/github.com/urfave/cli/v3/mkdocs.yml
generated
vendored
Normal file
139
vendor/github.com/urfave/cli/v3/mkdocs.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# NOTE: the mkdocs dependencies will need to be installed out of
|
||||
# band until this whole thing gets more automated:
|
||||
#
|
||||
# pip install -r mkdocs-reqs.txt
|
||||
#
|
||||
|
||||
site_name: urfave/cli
|
||||
site_url: https://cli.urfave.org/
|
||||
repo_url: https://github.com/urfave/cli
|
||||
edit_uri: edit/main/docs/
|
||||
nav:
|
||||
- Home:
|
||||
- Welcome: index.md
|
||||
- Contributing: CONTRIBUTING.md
|
||||
- Code of Conduct: CODE_OF_CONDUCT.md
|
||||
- Releasing: RELEASING.md
|
||||
- Security: SECURITY.md
|
||||
- Migrate v2 to v3: migrate-v2-to-v3.md
|
||||
- Migrate v1 to v2: migrate-v1-to-v2.md
|
||||
- v3 Manual:
|
||||
- Getting Started: v3/getting-started.md
|
||||
- Migrating From Older Releases: v3/migrating-from-older-releases.md
|
||||
- Examples:
|
||||
- Greet: v3/examples/greet.md
|
||||
- Flags:
|
||||
- Basics: v3/examples/flags/basics.md
|
||||
- Value Sources: v3/examples/flags/value-sources.md
|
||||
- Short Options: v3/examples/flags/short-options.md
|
||||
- Advanced: v3/examples/flags/advanced.md
|
||||
- Arguments:
|
||||
- Basics: v3/examples/arguments/basics.md
|
||||
- Advanced: v3/examples/arguments/advanced.md
|
||||
- Subcommands:
|
||||
- Basics: v3/examples/subcommands/basics.md
|
||||
- Categories: v3/examples/subcommands/categories.md
|
||||
- Completions:
|
||||
- Shell Completions: v3/examples/completions/shell-completions.md
|
||||
- Customizations: v3/examples/completions/customizations.md
|
||||
- Help Text:
|
||||
- Generated Help Text: v3/examples/help/generated-help-text.md
|
||||
- Suggestions: v3/examples/help/suggestions.md
|
||||
- Error Handling:
|
||||
- Exit Codes: v3/examples/exit-codes.md
|
||||
- Full API Example: v3/examples/full-api-example.md
|
||||
- v2 Manual:
|
||||
- Getting Started: v2/getting-started.md
|
||||
- Migrating to v3: v2/migrating-to-v3.md
|
||||
- Migrating From Older Releases: v2/migrating-from-older-releases.md
|
||||
- Examples:
|
||||
- Greet: v2/examples/greet.md
|
||||
- Arguments: v2/examples/arguments.md
|
||||
- Flags: v2/examples/flags.md
|
||||
- Subcommands: v2/examples/subcommands.md
|
||||
- Subcommands Categories: v2/examples/subcommands-categories.md
|
||||
- Exit Codes: v2/examples/exit-codes.md
|
||||
- Combining Short Options: v2/examples/combining-short-options.md
|
||||
- Bash Completions: v2/examples/bash-completions.md
|
||||
- Generated Help Text: v2/examples/generated-help-text.md
|
||||
- Version Flag: v2/examples/version-flag.md
|
||||
- Timestamp Flag: v2/examples/timestamp-flag.md
|
||||
- Suggestions: v2/examples/suggestions.md
|
||||
- Full API Example: v2/examples/full-api-example.md
|
||||
- v1 Manual:
|
||||
- Getting Started: v1/getting-started.md
|
||||
- Migrating to v2: v1/migrating-to-v2.md
|
||||
- Examples:
|
||||
- Greet: v1/examples/greet.md
|
||||
- Arguments: v1/examples/arguments.md
|
||||
- Flags: v1/examples/flags.md
|
||||
- Subcommands: v1/examples/subcommands.md
|
||||
- Subcommands (Categories): v1/examples/subcommands-categories.md
|
||||
- Exit Codes: v1/examples/exit-codes.md
|
||||
- Combining Short Options: v1/examples/combining-short-options.md
|
||||
- Bash Completions: v1/examples/bash-completions.md
|
||||
- Generated Help Text: v1/examples/generated-help-text.md
|
||||
- Version Flag: v1/examples/version-flag.md
|
||||
|
||||
theme:
|
||||
name: material
|
||||
palette:
|
||||
- media: "(prefers-color-scheme: light)"
|
||||
scheme: default
|
||||
toggle:
|
||||
icon: material/brightness-4
|
||||
name: dark mode
|
||||
- media: "(prefers-color-scheme: dark)"
|
||||
scheme: slate
|
||||
toggle:
|
||||
icon: material/brightness-7
|
||||
name: light mode
|
||||
features:
|
||||
- content.code.annotate
|
||||
- navigation.top
|
||||
- navigation.instant
|
||||
- navigation.expand
|
||||
- navigation.sections
|
||||
- navigation.tabs
|
||||
- navigation.tabs.sticky
|
||||
|
||||
plugins:
|
||||
- git-revision-date-localized
|
||||
- search
|
||||
- redirects:
|
||||
redirect_maps:
|
||||
'v3/examples/bash-completions.md': 'v3/examples/completions/shell-completions.md'
|
||||
- tags
|
||||
|
||||
# NOTE: this is the recommended configuration from
|
||||
# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration
|
||||
markdown_extensions:
|
||||
- abbr
|
||||
- admonition
|
||||
- attr_list
|
||||
- def_list
|
||||
- footnotes
|
||||
- meta
|
||||
- md_in_html
|
||||
- toc:
|
||||
permalink: true
|
||||
- pymdownx.arithmatex:
|
||||
generic: true
|
||||
- pymdownx.betterem:
|
||||
smart_enable: all
|
||||
- pymdownx.caret
|
||||
- pymdownx.details
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
- pymdownx.highlight
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.keys
|
||||
- pymdownx.mark
|
||||
- pymdownx.smartsymbols
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tabbed:
|
||||
alternate_style: true
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.tilde
|
||||
29
vendor/github.com/urfave/cli/v3/sort.go
generated
vendored
Normal file
29
vendor/github.com/urfave/cli/v3/sort.go
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package cli
|
||||
|
||||
import "unicode"
|
||||
|
||||
// lexicographicLess compares strings alphabetically considering case.
|
||||
func lexicographicLess(i, j string) bool {
|
||||
iRunes := []rune(i)
|
||||
jRunes := []rune(j)
|
||||
|
||||
lenShared := len(iRunes)
|
||||
if lenShared > len(jRunes) {
|
||||
lenShared = len(jRunes)
|
||||
}
|
||||
|
||||
for index := 0; index < lenShared; index++ {
|
||||
ir := iRunes[index]
|
||||
jr := jRunes[index]
|
||||
|
||||
if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr {
|
||||
return lir < ljr
|
||||
}
|
||||
|
||||
if ir != jr {
|
||||
return ir < jr
|
||||
}
|
||||
}
|
||||
|
||||
return i < j
|
||||
}
|
||||
1
vendor/github.com/urfave/cli/v3/staticcheck.conf
generated
vendored
Normal file
1
vendor/github.com/urfave/cli/v3/staticcheck.conf
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
checks=["all"]
|
||||
147
vendor/github.com/urfave/cli/v3/suggestions.go
generated
vendored
Normal file
147
vendor/github.com/urfave/cli/v3/suggestions.go
generated
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
const suggestDidYouMeanTemplate = "Did you mean %q?"
|
||||
|
||||
var (
|
||||
SuggestFlag SuggestFlagFunc = suggestFlag
|
||||
SuggestCommand SuggestCommandFunc = suggestCommand
|
||||
SuggestDidYouMeanTemplate string = suggestDidYouMeanTemplate
|
||||
)
|
||||
|
||||
type SuggestFlagFunc func(flags []Flag, provided string, hideHelp bool) string
|
||||
|
||||
type SuggestCommandFunc func(commands []*Command, provided string) string
|
||||
|
||||
// jaroDistance is the measure of similarity between two strings. It returns a
|
||||
// value between 0 and 1, where 1 indicates identical strings and 0 indicates
|
||||
// completely different strings.
|
||||
//
|
||||
// Adapted from https://github.com/xrash/smetrics/blob/5f08fbb34913bc8ab95bb4f2a89a0637ca922666/jaro.go.
|
||||
func jaroDistance(a, b string) float64 {
|
||||
if len(a) == 0 && len(b) == 0 {
|
||||
return 1
|
||||
}
|
||||
if len(a) == 0 || len(b) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
lenA := float64(len(a))
|
||||
lenB := float64(len(b))
|
||||
hashA := make([]bool, len(a))
|
||||
hashB := make([]bool, len(b))
|
||||
maxDistance := int(math.Max(0, math.Floor(math.Max(lenA, lenB)/2.0)-1))
|
||||
|
||||
var matches float64
|
||||
for i := 0; i < len(a); i++ {
|
||||
start := int(math.Max(0, float64(i-maxDistance)))
|
||||
end := int(math.Min(lenB-1, float64(i+maxDistance)))
|
||||
|
||||
for j := start; j <= end; j++ {
|
||||
if hashB[j] {
|
||||
continue
|
||||
}
|
||||
if a[i] == b[j] {
|
||||
hashA[i] = true
|
||||
hashB[j] = true
|
||||
matches++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if matches == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var transpositions float64
|
||||
var j int
|
||||
for i := 0; i < len(a); i++ {
|
||||
if !hashA[i] {
|
||||
continue
|
||||
}
|
||||
for !hashB[j] {
|
||||
j++
|
||||
}
|
||||
if a[i] != b[j] {
|
||||
transpositions++
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
transpositions /= 2
|
||||
return ((matches / lenA) + (matches / lenB) + ((matches - transpositions) / matches)) / 3.0
|
||||
}
|
||||
|
||||
// jaroWinkler is more accurate when strings have a common prefix up to a
|
||||
// defined maximum length.
|
||||
//
|
||||
// Adapted from https://github.com/xrash/smetrics/blob/5f08fbb34913bc8ab95bb4f2a89a0637ca922666/jaro-winkler.go.
|
||||
func jaroWinkler(a, b string) float64 {
|
||||
const (
|
||||
boostThreshold = 0.7
|
||||
prefixSize = 4
|
||||
)
|
||||
jaroDist := jaroDistance(a, b)
|
||||
if jaroDist <= boostThreshold {
|
||||
return jaroDist
|
||||
}
|
||||
|
||||
prefix := int(math.Min(float64(len(a)), math.Min(float64(prefixSize), float64(len(b)))))
|
||||
|
||||
var prefixMatch float64
|
||||
for i := 0; i < prefix; i++ {
|
||||
if a[i] == b[i] {
|
||||
prefixMatch++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return jaroDist + 0.1*prefixMatch*(1.0-jaroDist)
|
||||
}
|
||||
|
||||
func suggestFlag(flags []Flag, provided string, hideHelp bool) string {
|
||||
distance := 0.0
|
||||
suggestion := ""
|
||||
|
||||
for _, flag := range flags {
|
||||
flagNames := flag.Names()
|
||||
if !hideHelp && HelpFlag != nil {
|
||||
flagNames = append(flagNames, HelpFlag.Names()...)
|
||||
}
|
||||
for _, name := range flagNames {
|
||||
newDistance := jaroWinkler(name, provided)
|
||||
if newDistance > distance {
|
||||
distance = newDistance
|
||||
suggestion = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(suggestion) == 1 {
|
||||
suggestion = "-" + suggestion
|
||||
} else if len(suggestion) > 1 {
|
||||
suggestion = "--" + suggestion
|
||||
}
|
||||
|
||||
return suggestion
|
||||
}
|
||||
|
||||
// suggestCommand takes a list of commands and a provided string to suggest a
|
||||
// command name
|
||||
func suggestCommand(commands []*Command, provided string) (suggestion string) {
|
||||
distance := 0.0
|
||||
for _, command := range commands {
|
||||
for _, name := range append(command.Names(), helpName, helpAlias) {
|
||||
newDistance := jaroWinkler(name, provided)
|
||||
if newDistance > distance {
|
||||
distance = newDistance
|
||||
suggestion = name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return suggestion
|
||||
}
|
||||
125
vendor/github.com/urfave/cli/v3/template.go
generated
vendored
Normal file
125
vendor/github.com/urfave/cli/v3/template.go
generated
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
package cli
|
||||
|
||||
var (
|
||||
helpNameTemplate = `{{$v := offset .FullName 6}}{{wrap .FullName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}`
|
||||
argsTemplate = `{{if .Arguments}}{{range .Arguments}}{{.Usage}}{{end}}{{end}}`
|
||||
usageTemplate = `{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}}{{if .VisibleFlags}} [options]{{end}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} {{template "argsTemplate" .}}{{end}}{{end}}{{end}}`
|
||||
descriptionTemplate = `{{wrap .Description 3}}`
|
||||
authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
|
||||
{{range $index, $author := .Authors}}{{if $index}}
|
||||
{{end}}{{$author}}{{end}}`
|
||||
)
|
||||
|
||||
var visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
|
||||
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}`
|
||||
|
||||
var visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}}
|
||||
|
||||
{{.Name}}:{{range .VisibleCommands}}
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}`
|
||||
|
||||
var visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}}
|
||||
{{if .Name}}{{.Name}}
|
||||
|
||||
{{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}}
|
||||
{{else}}{{$e}}
|
||||
{{end}}{{end}}{{end}}`
|
||||
|
||||
var visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}}
|
||||
{{wrap $e.String 6}}{{end}}`
|
||||
|
||||
var visiblePersistentFlagTemplate = `{{range $i, $e := .VisiblePersistentFlags}}
|
||||
{{wrap $e.String 6}}{{end}}`
|
||||
|
||||
var versionTemplate = `{{if .Version}}{{if not .HideVersion}}
|
||||
|
||||
VERSION:
|
||||
{{.Version}}{{end}}{{end}}`
|
||||
|
||||
var copyrightTemplate = `{{wrap .Copyright 3}}`
|
||||
|
||||
// RootCommandHelpTemplate is the text template for the Default help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var RootCommandHelpTemplate = `NAME:
|
||||
{{template "helpNameTemplate" .}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}} {{if .VisibleFlags}}[global options]{{end}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
|
||||
|
||||
VERSION:
|
||||
{{.Version}}{{end}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{template "descriptionTemplate" .}}{{end}}
|
||||
{{- if len .Authors}}
|
||||
|
||||
AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}}
|
||||
|
||||
COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
|
||||
|
||||
GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
|
||||
|
||||
GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}}
|
||||
|
||||
COPYRIGHT:
|
||||
{{template "copyrightTemplate" .}}{{end}}
|
||||
`
|
||||
|
||||
// CommandHelpTemplate is the text template for the command help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var CommandHelpTemplate = `NAME:
|
||||
{{template "helpNameTemplate" .}}
|
||||
|
||||
USAGE:
|
||||
{{template "usageTemplate" .}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
|
||||
|
||||
OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
|
||||
|
||||
OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .VisiblePersistentFlags}}
|
||||
|
||||
GLOBAL OPTIONS:{{template "visiblePersistentFlagTemplate" .}}{{end}}
|
||||
`
|
||||
|
||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
||||
// cli.go uses text/template to render templates. You can
|
||||
// render custom help text by setting this variable.
|
||||
var SubcommandHelpTemplate = `NAME:
|
||||
{{template "helpNameTemplate" .}}
|
||||
|
||||
USAGE:
|
||||
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Category}}
|
||||
|
||||
CATEGORY:
|
||||
{{.Category}}{{end}}{{if .Description}}
|
||||
|
||||
DESCRIPTION:
|
||||
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}}
|
||||
|
||||
COMMANDS:{{template "visibleCommandTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
|
||||
|
||||
OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
|
||||
|
||||
OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}
|
||||
`
|
||||
|
||||
var FishCompletionTemplate = `# {{ .Command.Name }} fish shell completion
|
||||
|
||||
function __fish_{{ .Command.Name }}_no_subcommand --description 'Test if there has been any subcommand yet'
|
||||
for i in (commandline -opc)
|
||||
if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }}
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
{{ range $v := .Completions }}{{ $v }}
|
||||
{{ end }}`
|
||||
257
vendor/github.com/urfave/cli/v3/value_source.go
generated
vendored
Normal file
257
vendor/github.com/urfave/cli/v3/value_source.go
generated
vendored
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ValueSource is a source which can be used to look up a value,
|
||||
// typically for use with a cli.Flag
|
||||
type ValueSource interface {
|
||||
fmt.Stringer
|
||||
fmt.GoStringer
|
||||
|
||||
// Lookup returns the value from the source and if it was found
|
||||
// or returns an empty string and false
|
||||
Lookup() (string, bool)
|
||||
}
|
||||
|
||||
// EnvValueSource is to specifically detect env sources when
|
||||
// printing help text
|
||||
type EnvValueSource interface {
|
||||
IsFromEnv() bool
|
||||
Key() string
|
||||
}
|
||||
|
||||
// MapSource is a source which can be used to look up a value
|
||||
// based on a key
|
||||
// typically for use with a cli.Flag
|
||||
type MapSource interface {
|
||||
fmt.Stringer
|
||||
fmt.GoStringer
|
||||
|
||||
// Lookup returns the value from the source based on key
|
||||
// and if it was found
|
||||
// or returns an empty string and false
|
||||
Lookup(string) (any, bool)
|
||||
}
|
||||
|
||||
// ValueSourceChain contains an ordered series of ValueSource that
|
||||
// allows for lookup where the first ValueSource to resolve is
|
||||
// returned
|
||||
type ValueSourceChain struct {
|
||||
Chain []ValueSource
|
||||
}
|
||||
|
||||
func NewValueSourceChain(src ...ValueSource) ValueSourceChain {
|
||||
return ValueSourceChain{
|
||||
Chain: src,
|
||||
}
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) Append(other ValueSourceChain) {
|
||||
vsc.Chain = append(vsc.Chain, other.Chain...)
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) EnvKeys() []string {
|
||||
vals := []string{}
|
||||
|
||||
for _, src := range vsc.Chain {
|
||||
if v, ok := src.(EnvValueSource); ok && v.IsFromEnv() {
|
||||
vals = append(vals, v.Key())
|
||||
}
|
||||
}
|
||||
|
||||
return vals
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) String() string {
|
||||
s := []string{}
|
||||
|
||||
for _, vs := range vsc.Chain {
|
||||
s = append(s, vs.String())
|
||||
}
|
||||
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) GoString() string {
|
||||
s := []string{}
|
||||
|
||||
for _, vs := range vsc.Chain {
|
||||
s = append(s, vs.GoString())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("&ValueSourceChain{Chain:{%[1]s}}", strings.Join(s, ","))
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) Lookup() (string, bool) {
|
||||
s, _, ok := vsc.LookupWithSource()
|
||||
return s, ok
|
||||
}
|
||||
|
||||
func (vsc *ValueSourceChain) LookupWithSource() (string, ValueSource, bool) {
|
||||
for _, src := range vsc.Chain {
|
||||
if value, found := src.Lookup(); found {
|
||||
return value, src, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil, false
|
||||
}
|
||||
|
||||
// envVarValueSource encapsulates a ValueSource from an environment variable
|
||||
type envVarValueSource struct {
|
||||
key string
|
||||
}
|
||||
|
||||
func (e *envVarValueSource) Lookup() (string, bool) {
|
||||
return os.LookupEnv(strings.TrimSpace(string(e.key)))
|
||||
}
|
||||
|
||||
func (e *envVarValueSource) IsFromEnv() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *envVarValueSource) Key() string {
|
||||
return e.key
|
||||
}
|
||||
|
||||
func (e *envVarValueSource) String() string { return fmt.Sprintf("environment variable %[1]q", e.key) }
|
||||
func (e *envVarValueSource) GoString() string {
|
||||
return fmt.Sprintf("&envVarValueSource{Key:%[1]q}", e.key)
|
||||
}
|
||||
|
||||
func EnvVar(key string) ValueSource {
|
||||
return &envVarValueSource{
|
||||
key: key,
|
||||
}
|
||||
}
|
||||
|
||||
// EnvVars is a helper function to encapsulate a number of
|
||||
// envVarValueSource together as a ValueSourceChain
|
||||
func EnvVars(keys ...string) ValueSourceChain {
|
||||
vsc := ValueSourceChain{Chain: []ValueSource{}}
|
||||
|
||||
for _, key := range keys {
|
||||
vsc.Chain = append(vsc.Chain, EnvVar(key))
|
||||
}
|
||||
|
||||
return vsc
|
||||
}
|
||||
|
||||
// fileValueSource encapsulates a ValueSource from a file
|
||||
type fileValueSource struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
func (f *fileValueSource) Lookup() (string, bool) {
|
||||
data, err := os.ReadFile(f.Path)
|
||||
return string(data), err == nil
|
||||
}
|
||||
|
||||
func (f *fileValueSource) String() string { return fmt.Sprintf("file %[1]q", f.Path) }
|
||||
func (f *fileValueSource) GoString() string {
|
||||
return fmt.Sprintf("&fileValueSource{Path:%[1]q}", f.Path)
|
||||
}
|
||||
|
||||
func File(path string) ValueSource {
|
||||
return &fileValueSource{Path: path}
|
||||
}
|
||||
|
||||
// Files is a helper function to encapsulate a number of
|
||||
// fileValueSource together as a ValueSourceChain
|
||||
func Files(paths ...string) ValueSourceChain {
|
||||
vsc := ValueSourceChain{Chain: []ValueSource{}}
|
||||
|
||||
for _, path := range paths {
|
||||
vsc.Chain = append(vsc.Chain, File(path))
|
||||
}
|
||||
|
||||
return vsc
|
||||
}
|
||||
|
||||
type mapSource struct {
|
||||
name string
|
||||
m map[any]any
|
||||
}
|
||||
|
||||
func NewMapSource(name string, m map[any]any) MapSource {
|
||||
return &mapSource{
|
||||
name: name,
|
||||
m: m,
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *mapSource) String() string { return fmt.Sprintf("map source %[1]q", ms.name) }
|
||||
func (ms *mapSource) GoString() string {
|
||||
return fmt.Sprintf("&mapSource{name:%[1]q}", ms.name)
|
||||
}
|
||||
|
||||
// Lookup returns a value from the map source. The lookup name may be a dot-separated path into the map.
|
||||
// If that is the case, it will recursively traverse the map based on the '.' delimited sections to find
|
||||
// a nested value for the key.
|
||||
func (ms *mapSource) Lookup(name string) (any, bool) {
|
||||
sections := strings.Split(name, ".")
|
||||
if name == "" || len(sections) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
node := ms.m
|
||||
|
||||
// traverse into the map based on the dot-separated sections
|
||||
if len(sections) >= 2 { // the last section is the value we want, we will return it directly at the end
|
||||
for _, section := range sections[:len(sections)-1] {
|
||||
child, ok := node[section]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch child := child.(type) {
|
||||
case map[string]any:
|
||||
node = make(map[any]any, len(child))
|
||||
for k, v := range child {
|
||||
node[k] = v
|
||||
}
|
||||
case map[any]any:
|
||||
node = child
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := node[sections[len(sections)-1]]; ok {
|
||||
return val, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type mapValueSource struct {
|
||||
key string
|
||||
ms MapSource
|
||||
}
|
||||
|
||||
func NewMapValueSource(key string, ms MapSource) ValueSource {
|
||||
return &mapValueSource{
|
||||
key: key,
|
||||
ms: ms,
|
||||
}
|
||||
}
|
||||
|
||||
func (mvs *mapValueSource) String() string {
|
||||
return fmt.Sprintf("key %[1]q from %[2]s", mvs.key, mvs.ms.String())
|
||||
}
|
||||
|
||||
func (mvs *mapValueSource) GoString() string {
|
||||
return fmt.Sprintf("&mapValueSource{key:%[1]q, src:%[2]s}", mvs.key, mvs.ms.GoString())
|
||||
}
|
||||
|
||||
func (mvs *mapValueSource) Lookup() (string, bool) {
|
||||
if v, ok := mvs.ms.Lookup(mvs.key); !ok {
|
||||
return "", false
|
||||
} else {
|
||||
return fmt.Sprintf("%+v", v), true
|
||||
}
|
||||
}
|
||||
20
vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE
generated
vendored
Normal file
20
vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexandre Cesaro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
16
vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md
generated
vendored
Normal file
16
vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# quotedprintable
|
||||
|
||||
## Introduction
|
||||
|
||||
Package quotedprintable implements quoted-printable and message header encoding
|
||||
as specified by RFC 2045 and RFC 2047.
|
||||
|
||||
It is a copy of the Go 1.5 package `mime/quotedprintable`. It also includes
|
||||
the new functions of package `mime` concerning RFC 2047.
|
||||
|
||||
This code has minor changes with the standard library code in order to work
|
||||
with Go 1.0 and newer.
|
||||
|
||||
## Documentation
|
||||
|
||||
https://godoc.org/gopkg.in/alexcesaro/quotedprintable.v3
|
||||
279
vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go
generated
vendored
Normal file
279
vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go
generated
vendored
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
package quotedprintable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// A WordEncoder is a RFC 2047 encoded-word encoder.
|
||||
type WordEncoder byte
|
||||
|
||||
const (
|
||||
// BEncoding represents Base64 encoding scheme as defined by RFC 2045.
|
||||
BEncoding = WordEncoder('b')
|
||||
// QEncoding represents the Q-encoding scheme as defined by RFC 2047.
|
||||
QEncoding = WordEncoder('q')
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidWord = errors.New("mime: invalid RFC 2047 encoded-word")
|
||||
)
|
||||
|
||||
// Encode returns the encoded-word form of s. If s is ASCII without special
|
||||
// characters, it is returned unchanged. The provided charset is the IANA
|
||||
// charset name of s. It is case insensitive.
|
||||
func (e WordEncoder) Encode(charset, s string) string {
|
||||
if !needsEncoding(s) {
|
||||
return s
|
||||
}
|
||||
return e.encodeWord(charset, s)
|
||||
}
|
||||
|
||||
func needsEncoding(s string) bool {
|
||||
for _, b := range s {
|
||||
if (b < ' ' || b > '~') && b != '\t' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// encodeWord encodes a string into an encoded-word.
|
||||
func (e WordEncoder) encodeWord(charset, s string) string {
|
||||
buf := getBuffer()
|
||||
defer putBuffer(buf)
|
||||
|
||||
buf.WriteString("=?")
|
||||
buf.WriteString(charset)
|
||||
buf.WriteByte('?')
|
||||
buf.WriteByte(byte(e))
|
||||
buf.WriteByte('?')
|
||||
|
||||
if e == BEncoding {
|
||||
w := base64.NewEncoder(base64.StdEncoding, buf)
|
||||
io.WriteString(w, s)
|
||||
w.Close()
|
||||
} else {
|
||||
enc := make([]byte, 3)
|
||||
for i := 0; i < len(s); i++ {
|
||||
b := s[i]
|
||||
switch {
|
||||
case b == ' ':
|
||||
buf.WriteByte('_')
|
||||
case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
|
||||
buf.WriteByte(b)
|
||||
default:
|
||||
enc[0] = '='
|
||||
enc[1] = upperhex[b>>4]
|
||||
enc[2] = upperhex[b&0x0f]
|
||||
buf.Write(enc)
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.WriteString("?=")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
const upperhex = "0123456789ABCDEF"
|
||||
|
||||
// A WordDecoder decodes MIME headers containing RFC 2047 encoded-words.
|
||||
type WordDecoder struct {
|
||||
// CharsetReader, if non-nil, defines a function to generate
|
||||
// charset-conversion readers, converting from the provided
|
||||
// charset into UTF-8.
|
||||
// Charsets are always lower-case. utf-8, iso-8859-1 and us-ascii charsets
|
||||
// are handled by default.
|
||||
// One of the the CharsetReader's result values must be non-nil.
|
||||
CharsetReader func(charset string, input io.Reader) (io.Reader, error)
|
||||
}
|
||||
|
||||
// Decode decodes an encoded-word. If word is not a valid RFC 2047 encoded-word,
|
||||
// word is returned unchanged.
|
||||
func (d *WordDecoder) Decode(word string) (string, error) {
|
||||
fields := strings.Split(word, "?") // TODO: remove allocation?
|
||||
if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" || len(fields[2]) != 1 {
|
||||
return "", errInvalidWord
|
||||
}
|
||||
|
||||
content, err := decode(fields[2][0], fields[3])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf := getBuffer()
|
||||
defer putBuffer(buf)
|
||||
|
||||
if err := d.convert(buf, fields[1], content); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// DecodeHeader decodes all encoded-words of the given string. It returns an
|
||||
// error if and only if CharsetReader of d returns an error.
|
||||
func (d *WordDecoder) DecodeHeader(header string) (string, error) {
|
||||
// If there is no encoded-word, returns before creating a buffer.
|
||||
i := strings.Index(header, "=?")
|
||||
if i == -1 {
|
||||
return header, nil
|
||||
}
|
||||
|
||||
buf := getBuffer()
|
||||
defer putBuffer(buf)
|
||||
|
||||
buf.WriteString(header[:i])
|
||||
header = header[i:]
|
||||
|
||||
betweenWords := false
|
||||
for {
|
||||
start := strings.Index(header, "=?")
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
cur := start + len("=?")
|
||||
|
||||
i := strings.Index(header[cur:], "?")
|
||||
if i == -1 {
|
||||
break
|
||||
}
|
||||
charset := header[cur : cur+i]
|
||||
cur += i + len("?")
|
||||
|
||||
if len(header) < cur+len("Q??=") {
|
||||
break
|
||||
}
|
||||
encoding := header[cur]
|
||||
cur++
|
||||
|
||||
if header[cur] != '?' {
|
||||
break
|
||||
}
|
||||
cur++
|
||||
|
||||
j := strings.Index(header[cur:], "?=")
|
||||
if j == -1 {
|
||||
break
|
||||
}
|
||||
text := header[cur : cur+j]
|
||||
end := cur + j + len("?=")
|
||||
|
||||
content, err := decode(encoding, text)
|
||||
if err != nil {
|
||||
betweenWords = false
|
||||
buf.WriteString(header[:start+2])
|
||||
header = header[start+2:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Write characters before the encoded-word. White-space and newline
|
||||
// characters separating two encoded-words must be deleted.
|
||||
if start > 0 && (!betweenWords || hasNonWhitespace(header[:start])) {
|
||||
buf.WriteString(header[:start])
|
||||
}
|
||||
|
||||
if err := d.convert(buf, charset, content); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
header = header[end:]
|
||||
betweenWords = true
|
||||
}
|
||||
|
||||
if len(header) > 0 {
|
||||
buf.WriteString(header)
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func decode(encoding byte, text string) ([]byte, error) {
|
||||
switch encoding {
|
||||
case 'B', 'b':
|
||||
return base64.StdEncoding.DecodeString(text)
|
||||
case 'Q', 'q':
|
||||
return qDecode(text)
|
||||
}
|
||||
return nil, errInvalidWord
|
||||
}
|
||||
|
||||
func (d *WordDecoder) convert(buf *bytes.Buffer, charset string, content []byte) error {
|
||||
switch {
|
||||
case strings.EqualFold("utf-8", charset):
|
||||
buf.Write(content)
|
||||
case strings.EqualFold("iso-8859-1", charset):
|
||||
for _, c := range content {
|
||||
buf.WriteRune(rune(c))
|
||||
}
|
||||
case strings.EqualFold("us-ascii", charset):
|
||||
for _, c := range content {
|
||||
if c >= utf8.RuneSelf {
|
||||
buf.WriteRune(unicode.ReplacementChar)
|
||||
} else {
|
||||
buf.WriteByte(c)
|
||||
}
|
||||
}
|
||||
default:
|
||||
if d.CharsetReader == nil {
|
||||
return fmt.Errorf("mime: unhandled charset %q", charset)
|
||||
}
|
||||
r, err := d.CharsetReader(strings.ToLower(charset), bytes.NewReader(content))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = buf.ReadFrom(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasNonWhitespace reports whether s (assumed to be ASCII) contains at least
|
||||
// one byte of non-whitespace.
|
||||
func hasNonWhitespace(s string) bool {
|
||||
for _, b := range s {
|
||||
switch b {
|
||||
// Encoded-words can only be separated by linear white spaces which does
|
||||
// not include vertical tabs (\v).
|
||||
case ' ', '\t', '\n', '\r':
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// qDecode decodes a Q encoded string.
|
||||
func qDecode(s string) ([]byte, error) {
|
||||
dec := make([]byte, len(s))
|
||||
n := 0
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch c := s[i]; {
|
||||
case c == '_':
|
||||
dec[n] = ' '
|
||||
case c == '=':
|
||||
if i+2 >= len(s) {
|
||||
return nil, errInvalidWord
|
||||
}
|
||||
b, err := readHexByte(s[i+1], s[i+2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec[n] = b
|
||||
i += 2
|
||||
case (c <= '~' && c >= ' ') || c == '\n' || c == '\r' || c == '\t':
|
||||
dec[n] = c
|
||||
default:
|
||||
return nil, errInvalidWord
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
return dec[:n], nil
|
||||
}
|
||||
26
vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go
generated
vendored
Normal file
26
vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// +build go1.3
|
||||
|
||||
package quotedprintable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var bufPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufPool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
if buf.Len() > 1024 {
|
||||
return
|
||||
}
|
||||
buf.Reset()
|
||||
bufPool.Put(buf)
|
||||
}
|
||||
24
vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go
generated
vendored
Normal file
24
vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// +build !go1.3
|
||||
|
||||
package quotedprintable
|
||||
|
||||
import "bytes"
|
||||
|
||||
var ch = make(chan *bytes.Buffer, 32)
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
select {
|
||||
case buf := <-ch:
|
||||
return buf
|
||||
default:
|
||||
}
|
||||
return new(bytes.Buffer)
|
||||
}
|
||||
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
buf.Reset()
|
||||
select {
|
||||
case ch <- buf:
|
||||
default:
|
||||
}
|
||||
}
|
||||
121
vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go
generated
vendored
Normal file
121
vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// Package quotedprintable implements quoted-printable encoding as specified by
|
||||
// RFC 2045.
|
||||
package quotedprintable
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Reader is a quoted-printable decoder.
|
||||
type Reader struct {
|
||||
br *bufio.Reader
|
||||
rerr error // last read error
|
||||
line []byte // to be consumed before more of br
|
||||
}
|
||||
|
||||
// NewReader returns a quoted-printable reader, decoding from r.
|
||||
func NewReader(r io.Reader) *Reader {
|
||||
return &Reader{
|
||||
br: bufio.NewReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
func fromHex(b byte) (byte, error) {
|
||||
switch {
|
||||
case b >= '0' && b <= '9':
|
||||
return b - '0', nil
|
||||
case b >= 'A' && b <= 'F':
|
||||
return b - 'A' + 10, nil
|
||||
// Accept badly encoded bytes.
|
||||
case b >= 'a' && b <= 'f':
|
||||
return b - 'a' + 10, nil
|
||||
}
|
||||
return 0, fmt.Errorf("quotedprintable: invalid hex byte 0x%02x", b)
|
||||
}
|
||||
|
||||
func readHexByte(a, b byte) (byte, error) {
|
||||
var hb, lb byte
|
||||
var err error
|
||||
if hb, err = fromHex(a); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if lb, err = fromHex(b); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return hb<<4 | lb, nil
|
||||
}
|
||||
|
||||
func isQPDiscardWhitespace(r rune) bool {
|
||||
switch r {
|
||||
case '\n', '\r', ' ', '\t':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
crlf = []byte("\r\n")
|
||||
lf = []byte("\n")
|
||||
softSuffix = []byte("=")
|
||||
)
|
||||
|
||||
// Read reads and decodes quoted-printable data from the underlying reader.
|
||||
func (r *Reader) Read(p []byte) (n int, err error) {
|
||||
// Deviations from RFC 2045:
|
||||
// 1. in addition to "=\r\n", "=\n" is also treated as soft line break.
|
||||
// 2. it will pass through a '\r' or '\n' not preceded by '=', consistent
|
||||
// with other broken QP encoders & decoders.
|
||||
for len(p) > 0 {
|
||||
if len(r.line) == 0 {
|
||||
if r.rerr != nil {
|
||||
return n, r.rerr
|
||||
}
|
||||
r.line, r.rerr = r.br.ReadSlice('\n')
|
||||
|
||||
// Does the line end in CRLF instead of just LF?
|
||||
hasLF := bytes.HasSuffix(r.line, lf)
|
||||
hasCR := bytes.HasSuffix(r.line, crlf)
|
||||
wholeLine := r.line
|
||||
r.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace)
|
||||
if bytes.HasSuffix(r.line, softSuffix) {
|
||||
rightStripped := wholeLine[len(r.line):]
|
||||
r.line = r.line[:len(r.line)-1]
|
||||
if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) {
|
||||
r.rerr = fmt.Errorf("quotedprintable: invalid bytes after =: %q", rightStripped)
|
||||
}
|
||||
} else if hasLF {
|
||||
if hasCR {
|
||||
r.line = append(r.line, '\r', '\n')
|
||||
} else {
|
||||
r.line = append(r.line, '\n')
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
b := r.line[0]
|
||||
|
||||
switch {
|
||||
case b == '=':
|
||||
if len(r.line[1:]) < 2 {
|
||||
return n, io.ErrUnexpectedEOF
|
||||
}
|
||||
b, err = readHexByte(r.line[1], r.line[2])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
r.line = r.line[2:] // 2 of the 3; other 1 is done below
|
||||
case b == '\t' || b == '\r' || b == '\n':
|
||||
break
|
||||
case b < ' ' || b > '~':
|
||||
return n, fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in body", b)
|
||||
}
|
||||
p[0] = b
|
||||
p = p[1:]
|
||||
r.line = r.line[1:]
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
166
vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go
generated
vendored
Normal file
166
vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
package quotedprintable
|
||||
|
||||
import "io"
|
||||
|
||||
const lineMaxLen = 76
|
||||
|
||||
// A Writer is a quoted-printable writer that implements io.WriteCloser.
|
||||
type Writer struct {
|
||||
// Binary mode treats the writer's input as pure binary and processes end of
|
||||
// line bytes as binary data.
|
||||
Binary bool
|
||||
|
||||
w io.Writer
|
||||
i int
|
||||
line [78]byte
|
||||
cr bool
|
||||
}
|
||||
|
||||
// NewWriter returns a new Writer that writes to w.
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return &Writer{w: w}
|
||||
}
|
||||
|
||||
// Write encodes p using quoted-printable encoding and writes it to the
|
||||
// underlying io.Writer. It limits line length to 76 characters. The encoded
|
||||
// bytes are not necessarily flushed until the Writer is closed.
|
||||
func (w *Writer) Write(p []byte) (n int, err error) {
|
||||
for i, b := range p {
|
||||
switch {
|
||||
// Simple writes are done in batch.
|
||||
case b >= '!' && b <= '~' && b != '=':
|
||||
continue
|
||||
case isWhitespace(b) || !w.Binary && (b == '\n' || b == '\r'):
|
||||
continue
|
||||
}
|
||||
|
||||
if i > n {
|
||||
if err := w.write(p[n:i]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n = i
|
||||
}
|
||||
|
||||
if err := w.encode(b); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
|
||||
if n == len(p) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
if err := w.write(p[n:]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Close closes the Writer, flushing any unwritten data to the underlying
|
||||
// io.Writer, but does not close the underlying io.Writer.
|
||||
func (w *Writer) Close() error {
|
||||
if err := w.checkLastByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.flush()
|
||||
}
|
||||
|
||||
// write limits text encoded in quoted-printable to 76 characters per line.
|
||||
func (w *Writer) write(p []byte) error {
|
||||
for _, b := range p {
|
||||
if b == '\n' || b == '\r' {
|
||||
// If the previous byte was \r, the CRLF has already been inserted.
|
||||
if w.cr && b == '\n' {
|
||||
w.cr = false
|
||||
continue
|
||||
}
|
||||
|
||||
if b == '\r' {
|
||||
w.cr = true
|
||||
}
|
||||
|
||||
if err := w.checkLastByte(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.insertCRLF(); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if w.i == lineMaxLen-1 {
|
||||
if err := w.insertSoftLineBreak(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w.line[w.i] = b
|
||||
w.i++
|
||||
w.cr = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) encode(b byte) error {
|
||||
if lineMaxLen-1-w.i < 3 {
|
||||
if err := w.insertSoftLineBreak(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w.line[w.i] = '='
|
||||
w.line[w.i+1] = upperhex[b>>4]
|
||||
w.line[w.i+2] = upperhex[b&0x0f]
|
||||
w.i += 3
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkLastByte encodes the last buffered byte if it is a space or a tab.
|
||||
func (w *Writer) checkLastByte() error {
|
||||
if w.i == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
b := w.line[w.i-1]
|
||||
if isWhitespace(b) {
|
||||
w.i--
|
||||
if err := w.encode(b); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Writer) insertSoftLineBreak() error {
|
||||
w.line[w.i] = '='
|
||||
w.i++
|
||||
|
||||
return w.insertCRLF()
|
||||
}
|
||||
|
||||
func (w *Writer) insertCRLF() error {
|
||||
w.line[w.i] = '\r'
|
||||
w.line[w.i+1] = '\n'
|
||||
w.i += 2
|
||||
|
||||
return w.flush()
|
||||
}
|
||||
|
||||
func (w *Writer) flush() error {
|
||||
if _, err := w.w.Write(w.line[:w.i]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.i = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func isWhitespace(b byte) bool {
|
||||
return b == ' ' || b == '\t'
|
||||
}
|
||||
17
vendor/gopkg.in/mail.v2/.gitignore
generated
vendored
Normal file
17
vendor/gopkg.in/mail.v2/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
|
||||
# IDE's
|
||||
.idea/
|
||||
25
vendor/gopkg.in/mail.v2/.travis.yml
generated
vendored
Normal file
25
vendor/gopkg.in/mail.v2/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- master
|
||||
|
||||
# safelist
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- v2
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
before_install:
|
||||
- mkdir -p $GOPATH/src/gopkg.in &&
|
||||
ln -s ../github.com/go-mail/mail $GOPATH/src/gopkg.in/mail.v2
|
||||
88
vendor/gopkg.in/mail.v2/CHANGELOG.md
generated
vendored
Normal file
88
vendor/gopkg.in/mail.v2/CHANGELOG.md
generated
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## *Unreleased*
|
||||
|
||||
## [2.3.1] - 2018-11-12
|
||||
|
||||
### Fixed
|
||||
|
||||
- #39: Reverts addition of Go modules `go.mod` manifest.
|
||||
|
||||
## [2.3.0] - 2018-11-10
|
||||
|
||||
### Added
|
||||
|
||||
- #12: Adds `SendError` to provide additional info about the cause and index of
|
||||
a failed attempt to transmit a batch of messages.
|
||||
- go-gomail#78: Adds new `Message` methods for attaching and embedding
|
||||
`io.Reader`s: `AttachReader` and `EmbedReader`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- #26: Fixes RFC 1341 compliance by properly capitalizing the
|
||||
`MIME-Version` header.
|
||||
- #30: Fixes IO errors being silently dropped in `Message.WriteTo`.
|
||||
|
||||
## [2.2.0] - 2018-03-01
|
||||
|
||||
### Added
|
||||
|
||||
- #20: Adds `Message.SetBoundary` to allow specifying a custom MIME boundary.
|
||||
- #22: Adds `Message.SetBodyWriter` to make it easy to use text/template and
|
||||
html/template for message bodies. Contributed by Quantcast.
|
||||
- #25: Adds `Dialer.StartTLSPolicy` so that `MandatoryStartTLS` can be required,
|
||||
or `NoStartTLS` can disable it. Contributed by Quantcast.
|
||||
|
||||
## [2.1.0] - 2017-12-14
|
||||
|
||||
### Added
|
||||
|
||||
- go-gomail#40: Adds `Dialer.LocalName` field to allow specifying the hostname
|
||||
sent with SMTP's HELO command.
|
||||
- go-gomail#47: `Message.SetBody`, `Message.AddAlternative`, and
|
||||
`Message.AddAlternativeWriter` allow specifying the encoding of message parts.
|
||||
- `Dialer.Dial`'s returned `SendCloser` automatically redials after a timeout.
|
||||
- go-gomail#55, go-gomail#56: Adds `Rename` to allow specifying filename
|
||||
of an attachment.
|
||||
- go-gomail#100: Exports `NetDialTimeout` to allow setting a custom dialer.
|
||||
- go-gomail#70: Adds `Dialer.Timeout` field to allow specifying a timeout for
|
||||
dials, reads, and writes.
|
||||
|
||||
### Changed
|
||||
|
||||
- go-gomail#52: `Dialer.Dial` automatically uses CRAM-MD5 when available.
|
||||
- `Dialer.Dial` specifies a default timeout of 10 seconds.
|
||||
- Gomail is forked from <https://github.com/go-gomail/gomail/> to
|
||||
<https://github.com/go-mail/mail/>.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- go-gomail#52: `NewPlainDialer` is deprecated in favor of `NewDialer`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- go-gomail#41, go-gomail#42: Fixes a panic when a `Message` contains a
|
||||
nil header.
|
||||
- go-gomail#44: Fixes `AddAlternativeWriter` replacing the message body instead
|
||||
of adding a body part.
|
||||
- go-gomail#53: Folds long header lines for RFC 2047 compliance.
|
||||
- go-gomail#54: Fixes `Message.FormatAddress` when name is blank.
|
||||
|
||||
## [2.0.0] - 2015-09-02
|
||||
|
||||
- Mailer has been removed. It has been replaced by Dialer and Sender.
|
||||
- `File` type and the `CreateFile` and `OpenFile` functions have been removed.
|
||||
- `Message.Attach` and `Message.Embed` have a new signature.
|
||||
- `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter`
|
||||
instead.
|
||||
- `Message.Export` has been removed. `Message.WriteTo` can be used instead.
|
||||
- `Message.DelHeader` has been removed.
|
||||
- The `Bcc` header field is no longer sent. It is far more simpler and
|
||||
efficient: the same message is sent to all recipients instead of sending a
|
||||
different email to each Bcc address.
|
||||
- LoginAuth has been removed. `NewPlainDialer` now implements the LOGIN
|
||||
authentication mechanism when needed.
|
||||
- Go 1.2 is now required instead of Go 1.3. No external dependency are used when
|
||||
using Go 1.5.
|
||||
20
vendor/gopkg.in/mail.v2/CONTRIBUTING.md
generated
vendored
Normal file
20
vendor/gopkg.in/mail.v2/CONTRIBUTING.md
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Thank you for contributing to Gomail! Here are a few guidelines:
|
||||
|
||||
## Bugs
|
||||
|
||||
If you think you found a bug, create an issue and supply the minimum amount
|
||||
of code triggering the bug so it can be reproduced.
|
||||
|
||||
|
||||
## Fixing a bug
|
||||
|
||||
If you want to fix a bug, you can send a pull request. It should contains a
|
||||
new test or update an existing one to cover that bug.
|
||||
|
||||
|
||||
## New feature proposal
|
||||
|
||||
If you think Gomail lacks a feature, you can open an issue or send a pull
|
||||
request. I want to keep Gomail code and API as simple as possible so please
|
||||
describe your needs so we can discuss whether this feature should be added to
|
||||
Gomail or not.
|
||||
20
vendor/gopkg.in/mail.v2/LICENSE
generated
vendored
Normal file
20
vendor/gopkg.in/mail.v2/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Alexandre Cesaro
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
129
vendor/gopkg.in/mail.v2/README.md
generated
vendored
Normal file
129
vendor/gopkg.in/mail.v2/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# Gomail
|
||||
[](https://travis-ci.org/go-mail/mail) [](http://gocover.io/github.com/go-mail/mail) [](https://godoc.org/github.com/go-mail/mail)
|
||||
|
||||
This is an actively maintained fork of [Gomail][1] and includes fixes and
|
||||
improvements for a number of outstanding issues. The current progress is
|
||||
as follows:
|
||||
|
||||
- [x] Timeouts and retries can be specified outside of the 10 second default.
|
||||
- [x] Proxying is supported through specifying a custom [NetDialTimeout][2].
|
||||
- [ ] Filenames are properly encoded for non-ASCII characters.
|
||||
- [ ] Email addresses are properly encoded for non-ASCII characters.
|
||||
- [ ] Embedded files and attachments are tested for their existence.
|
||||
- [ ] An `io.Reader` can be supplied when embedding and attaching files.
|
||||
|
||||
See [Transitioning Existing Codebases][3] for more information on switching.
|
||||
|
||||
[1]: https://github.com/go-gomail/gomail
|
||||
[2]: https://godoc.org/gopkg.in/mail.v2#NetDialTimeout
|
||||
[3]: #transitioning-existing-codebases
|
||||
|
||||
## Introduction
|
||||
|
||||
Gomail is a simple and efficient package to send emails. It is well tested and
|
||||
documented.
|
||||
|
||||
Gomail can only send emails using an SMTP server. But the API is flexible and it
|
||||
is easy to implement other methods for sending emails using a local Postfix, an
|
||||
API, etc.
|
||||
|
||||
It requires Go 1.2 or newer. With Go 1.5, no external dependencies are used.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
Gomail supports:
|
||||
- Attachments
|
||||
- Embedded images
|
||||
- HTML and text templates
|
||||
- Automatic encoding of special characters
|
||||
- SSL and TLS
|
||||
- Sending multiple emails with the same SMTP connection
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
https://godoc.org/github.com/go-mail/mail
|
||||
|
||||
|
||||
## Download
|
||||
|
||||
If you're already using a dependency manager, like [dep][dep], use the following
|
||||
import path:
|
||||
|
||||
```
|
||||
github.com/go-mail/mail
|
||||
```
|
||||
|
||||
If you *aren't* using vendoring, `go get` the [Gopkg.in](http://gopkg.in)
|
||||
import path:
|
||||
|
||||
```
|
||||
gopkg.in/mail.v2
|
||||
```
|
||||
|
||||
[dep]: https://github.com/golang/dep#readme
|
||||
|
||||
## Examples
|
||||
|
||||
See the [examples in the documentation](https://godoc.org/github.com/go-mail/mail#example-package).
|
||||
|
||||
|
||||
## FAQ
|
||||
|
||||
### x509: certificate signed by unknown authority
|
||||
|
||||
If you get this error it means the certificate used by the SMTP server is not
|
||||
considered valid by the client running Gomail. As a quick workaround you can
|
||||
bypass the verification of the server's certificate chain and host name by using
|
||||
`SetTLSConfig`:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"gopkg.in/mail.v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
d := mail.NewDialer("smtp.example.com", 587, "user", "123456")
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
|
||||
// Send emails using d.
|
||||
}
|
||||
```
|
||||
|
||||
Note, however, that this is insecure and should not be used in production.
|
||||
|
||||
### Transitioning Existing Codebases
|
||||
|
||||
If you're already using the original Gomail, switching is as easy as updating
|
||||
the import line to:
|
||||
|
||||
```
|
||||
import gomail "gopkg.in/mail.v2"
|
||||
```
|
||||
|
||||
## Contribute
|
||||
|
||||
Contributions are more than welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
|
||||
more info.
|
||||
|
||||
|
||||
## Change log
|
||||
|
||||
See [CHANGELOG.md](CHANGELOG.md).
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
|
||||
## Support & Contact
|
||||
|
||||
You can ask questions on the [Gomail
|
||||
thread](https://groups.google.com/d/topic/golang-nuts/jMxZHzvvEVg/discussion)
|
||||
in the Go mailing-list.
|
||||
49
vendor/gopkg.in/mail.v2/auth.go
generated
vendored
Normal file
49
vendor/gopkg.in/mail.v2/auth.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
// loginAuth is an smtp.Auth that implements the LOGIN authentication mechanism.
|
||||
type loginAuth struct {
|
||||
username string
|
||||
password string
|
||||
host string
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
if !server.TLS {
|
||||
advertised := false
|
||||
for _, mechanism := range server.Auth {
|
||||
if mechanism == "LOGIN" {
|
||||
advertised = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !advertised {
|
||||
return "", nil, errors.New("gomail: unencrypted connection")
|
||||
}
|
||||
}
|
||||
if server.Name != a.host {
|
||||
return "", nil, errors.New("gomail: wrong host name")
|
||||
}
|
||||
return "LOGIN", nil, nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if !more {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
case bytes.Equal(fromServer, []byte("Username:")):
|
||||
return []byte(a.username), nil
|
||||
case bytes.Equal(fromServer, []byte("Password:")):
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("gomail: unexpected server challenge: %s", fromServer)
|
||||
}
|
||||
}
|
||||
6
vendor/gopkg.in/mail.v2/doc.go
generated
vendored
Normal file
6
vendor/gopkg.in/mail.v2/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Package gomail provides a simple interface to compose emails and to mail them
|
||||
// efficiently.
|
||||
//
|
||||
// More info on Github: https://github.com/go-mail/mail
|
||||
//
|
||||
package mail
|
||||
16
vendor/gopkg.in/mail.v2/errors.go
generated
vendored
Normal file
16
vendor/gopkg.in/mail.v2/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package mail
|
||||
|
||||
import "fmt"
|
||||
|
||||
// A SendError represents the failure to transmit a Message, detailing the cause
|
||||
// of the failure and index of the Message within a batch.
|
||||
type SendError struct {
|
||||
// Index specifies the index of the Message within a batch.
|
||||
Index uint
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (err *SendError) Error() string {
|
||||
return fmt.Sprintf("gomail: could not send email %d: %v",
|
||||
err.Index+1, err.Cause)
|
||||
}
|
||||
359
vendor/gopkg.in/mail.v2/message.go
generated
vendored
Normal file
359
vendor/gopkg.in/mail.v2/message.go
generated
vendored
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Message represents an email.
|
||||
type Message struct {
|
||||
header header
|
||||
parts []*part
|
||||
attachments []*file
|
||||
embedded []*file
|
||||
charset string
|
||||
encoding Encoding
|
||||
hEncoder mimeEncoder
|
||||
buf bytes.Buffer
|
||||
boundary string
|
||||
}
|
||||
|
||||
type header map[string][]string
|
||||
|
||||
type part struct {
|
||||
contentType string
|
||||
copier func(io.Writer) error
|
||||
encoding Encoding
|
||||
}
|
||||
|
||||
// NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding
|
||||
// by default.
|
||||
func NewMessage(settings ...MessageSetting) *Message {
|
||||
m := &Message{
|
||||
header: make(header),
|
||||
charset: "UTF-8",
|
||||
encoding: QuotedPrintable,
|
||||
}
|
||||
|
||||
m.applySettings(settings)
|
||||
|
||||
if m.encoding == Base64 {
|
||||
m.hEncoder = bEncoding
|
||||
} else {
|
||||
m.hEncoder = qEncoding
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Reset resets the message so it can be reused. The message keeps its previous
|
||||
// settings so it is in the same state that after a call to NewMessage.
|
||||
func (m *Message) Reset() {
|
||||
for k := range m.header {
|
||||
delete(m.header, k)
|
||||
}
|
||||
m.parts = nil
|
||||
m.attachments = nil
|
||||
m.embedded = nil
|
||||
}
|
||||
|
||||
func (m *Message) applySettings(settings []MessageSetting) {
|
||||
for _, s := range settings {
|
||||
s(m)
|
||||
}
|
||||
}
|
||||
|
||||
// A MessageSetting can be used as an argument in NewMessage to configure an
|
||||
// email.
|
||||
type MessageSetting func(m *Message)
|
||||
|
||||
// SetCharset is a message setting to set the charset of the email.
|
||||
func SetCharset(charset string) MessageSetting {
|
||||
return func(m *Message) {
|
||||
m.charset = charset
|
||||
}
|
||||
}
|
||||
|
||||
// SetEncoding is a message setting to set the encoding of the email.
|
||||
func SetEncoding(enc Encoding) MessageSetting {
|
||||
return func(m *Message) {
|
||||
m.encoding = enc
|
||||
}
|
||||
}
|
||||
|
||||
// Encoding represents a MIME encoding scheme like quoted-printable or base64.
|
||||
type Encoding string
|
||||
|
||||
const (
|
||||
// QuotedPrintable represents the quoted-printable encoding as defined in
|
||||
// RFC 2045.
|
||||
QuotedPrintable Encoding = "quoted-printable"
|
||||
// Base64 represents the base64 encoding as defined in RFC 2045.
|
||||
Base64 Encoding = "base64"
|
||||
// Unencoded can be used to avoid encoding the body of an email. The headers
|
||||
// will still be encoded using quoted-printable encoding.
|
||||
Unencoded Encoding = "8bit"
|
||||
)
|
||||
|
||||
// SetBoundary sets a custom multipart boundary.
|
||||
func (m *Message) SetBoundary(boundary string) {
|
||||
m.boundary = boundary
|
||||
}
|
||||
|
||||
// SetHeader sets a value to the given header field.
|
||||
func (m *Message) SetHeader(field string, value ...string) {
|
||||
m.encodeHeader(value)
|
||||
m.header[field] = value
|
||||
}
|
||||
|
||||
func (m *Message) encodeHeader(values []string) {
|
||||
for i := range values {
|
||||
values[i] = m.encodeString(values[i])
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Message) encodeString(value string) string {
|
||||
return m.hEncoder.Encode(m.charset, value)
|
||||
}
|
||||
|
||||
// SetHeaders sets the message headers.
|
||||
func (m *Message) SetHeaders(h map[string][]string) {
|
||||
for k, v := range h {
|
||||
m.SetHeader(k, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAddressHeader sets an address to the given header field.
|
||||
func (m *Message) SetAddressHeader(field, address, name string) {
|
||||
m.header[field] = []string{m.FormatAddress(address, name)}
|
||||
}
|
||||
|
||||
// FormatAddress formats an address and a name as a valid RFC 5322 address.
|
||||
func (m *Message) FormatAddress(address, name string) string {
|
||||
if name == "" {
|
||||
return address
|
||||
}
|
||||
|
||||
enc := m.encodeString(name)
|
||||
if enc == name {
|
||||
m.buf.WriteByte('"')
|
||||
for i := 0; i < len(name); i++ {
|
||||
b := name[i]
|
||||
if b == '\\' || b == '"' {
|
||||
m.buf.WriteByte('\\')
|
||||
}
|
||||
m.buf.WriteByte(b)
|
||||
}
|
||||
m.buf.WriteByte('"')
|
||||
} else if hasSpecials(name) {
|
||||
m.buf.WriteString(bEncoding.Encode(m.charset, name))
|
||||
} else {
|
||||
m.buf.WriteString(enc)
|
||||
}
|
||||
m.buf.WriteString(" <")
|
||||
m.buf.WriteString(address)
|
||||
m.buf.WriteByte('>')
|
||||
|
||||
addr := m.buf.String()
|
||||
m.buf.Reset()
|
||||
return addr
|
||||
}
|
||||
|
||||
func hasSpecials(text string) bool {
|
||||
for i := 0; i < len(text); i++ {
|
||||
switch c := text[i]; c {
|
||||
case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '.', '"':
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// SetDateHeader sets a date to the given header field.
|
||||
func (m *Message) SetDateHeader(field string, date time.Time) {
|
||||
m.header[field] = []string{m.FormatDate(date)}
|
||||
}
|
||||
|
||||
// FormatDate formats a date as a valid RFC 5322 date.
|
||||
func (m *Message) FormatDate(date time.Time) string {
|
||||
return date.Format(time.RFC1123Z)
|
||||
}
|
||||
|
||||
// GetHeader gets a header field.
|
||||
func (m *Message) GetHeader(field string) []string {
|
||||
return m.header[field]
|
||||
}
|
||||
|
||||
// SetBody sets the body of the message. It replaces any content previously set
|
||||
// by SetBody, SetBodyWriter, AddAlternative or AddAlternativeWriter.
|
||||
func (m *Message) SetBody(contentType, body string, settings ...PartSetting) {
|
||||
m.SetBodyWriter(contentType, newCopier(body), settings...)
|
||||
}
|
||||
|
||||
// SetBodyWriter sets the body of the message. It can be useful with the
|
||||
// text/template or html/template packages.
|
||||
func (m *Message) SetBodyWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
|
||||
m.parts = []*part{m.newPart(contentType, f, settings)}
|
||||
}
|
||||
|
||||
// AddAlternative adds an alternative part to the message.
|
||||
//
|
||||
// It is commonly used to send HTML emails that default to the plain text
|
||||
// version for backward compatibility. AddAlternative appends the new part to
|
||||
// the end of the message. So the plain text part should be added before the
|
||||
// HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative
|
||||
func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) {
|
||||
m.AddAlternativeWriter(contentType, newCopier(body), settings...)
|
||||
}
|
||||
|
||||
func newCopier(s string) func(io.Writer) error {
|
||||
return func(w io.Writer) error {
|
||||
_, err := io.WriteString(w, s)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// AddAlternativeWriter adds an alternative part to the message. It can be
|
||||
// useful with the text/template or html/template packages.
|
||||
func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
|
||||
m.parts = append(m.parts, m.newPart(contentType, f, settings))
|
||||
}
|
||||
|
||||
func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part {
|
||||
p := &part{
|
||||
contentType: contentType,
|
||||
copier: f,
|
||||
encoding: m.encoding,
|
||||
}
|
||||
|
||||
for _, s := range settings {
|
||||
s(p)
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// A PartSetting can be used as an argument in Message.SetBody,
|
||||
// Message.SetBodyWriter, Message.AddAlternative or Message.AddAlternativeWriter
|
||||
// to configure the part added to a message.
|
||||
type PartSetting func(*part)
|
||||
|
||||
// SetPartEncoding sets the encoding of the part added to the message. By
|
||||
// default, parts use the same encoding than the message.
|
||||
func SetPartEncoding(e Encoding) PartSetting {
|
||||
return PartSetting(func(p *part) {
|
||||
p.encoding = e
|
||||
})
|
||||
}
|
||||
|
||||
type file struct {
|
||||
Name string
|
||||
Header map[string][]string
|
||||
CopyFunc func(w io.Writer) error
|
||||
}
|
||||
|
||||
func (f *file) setHeader(field, value string) {
|
||||
f.Header[field] = []string{value}
|
||||
}
|
||||
|
||||
// A FileSetting can be used as an argument in Message.Attach or Message.Embed.
|
||||
type FileSetting func(*file)
|
||||
|
||||
// SetHeader is a file setting to set the MIME header of the message part that
|
||||
// contains the file content.
|
||||
//
|
||||
// Mandatory headers are automatically added if they are not set when sending
|
||||
// the email.
|
||||
func SetHeader(h map[string][]string) FileSetting {
|
||||
return func(f *file) {
|
||||
for k, v := range h {
|
||||
f.Header[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rename is a file setting to set the name of the attachment if the name is
|
||||
// different than the filename on disk.
|
||||
func Rename(name string) FileSetting {
|
||||
return func(f *file) {
|
||||
f.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// SetCopyFunc is a file setting to replace the function that runs when the
|
||||
// message is sent. It should copy the content of the file to the io.Writer.
|
||||
//
|
||||
// The default copy function opens the file with the given filename, and copy
|
||||
// its content to the io.Writer.
|
||||
func SetCopyFunc(f func(io.Writer) error) FileSetting {
|
||||
return func(fi *file) {
|
||||
fi.CopyFunc = f
|
||||
}
|
||||
}
|
||||
|
||||
// AttachReader attaches a file using an io.Reader
|
||||
func (m *Message) AttachReader(name string, r io.Reader, settings ...FileSetting) {
|
||||
m.attachments = m.appendFile(m.attachments, fileFromReader(name, r), settings)
|
||||
}
|
||||
|
||||
// Attach attaches the files to the email.
|
||||
func (m *Message) Attach(filename string, settings ...FileSetting) {
|
||||
m.attachments = m.appendFile(m.attachments, fileFromFilename(filename), settings)
|
||||
}
|
||||
|
||||
// EmbedReader embeds the images to the email.
|
||||
func (m *Message) EmbedReader(name string, r io.Reader, settings ...FileSetting) {
|
||||
m.embedded = m.appendFile(m.embedded, fileFromReader(name, r), settings)
|
||||
}
|
||||
|
||||
// Embed embeds the images to the email.
|
||||
func (m *Message) Embed(filename string, settings ...FileSetting) {
|
||||
m.embedded = m.appendFile(m.embedded, fileFromFilename(filename), settings)
|
||||
}
|
||||
|
||||
func fileFromFilename(name string) *file {
|
||||
return &file{
|
||||
Name: filepath.Base(name),
|
||||
Header: make(map[string][]string),
|
||||
CopyFunc: func(w io.Writer) error {
|
||||
h, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(w, h); err != nil {
|
||||
h.Close()
|
||||
return err
|
||||
}
|
||||
return h.Close()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func fileFromReader(name string, r io.Reader) *file {
|
||||
return &file{
|
||||
Name: filepath.Base(name),
|
||||
Header: make(map[string][]string),
|
||||
CopyFunc: func(w io.Writer) error {
|
||||
if _, err := io.Copy(w, r); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Message) appendFile(list []*file, f *file, settings []FileSetting) []*file {
|
||||
for _, s := range settings {
|
||||
s(f)
|
||||
}
|
||||
|
||||
if list == nil {
|
||||
return []*file{f}
|
||||
}
|
||||
|
||||
return append(list, f)
|
||||
}
|
||||
21
vendor/gopkg.in/mail.v2/mime.go
generated
vendored
Normal file
21
vendor/gopkg.in/mail.v2/mime.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// +build go1.5
|
||||
|
||||
package mail
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"mime/quotedprintable"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var newQPWriter = quotedprintable.NewWriter
|
||||
|
||||
type mimeEncoder struct {
|
||||
mime.WordEncoder
|
||||
}
|
||||
|
||||
var (
|
||||
bEncoding = mimeEncoder{mime.BEncoding}
|
||||
qEncoding = mimeEncoder{mime.QEncoding}
|
||||
lastIndexByte = strings.LastIndexByte
|
||||
)
|
||||
25
vendor/gopkg.in/mail.v2/mime_go14.go
generated
vendored
Normal file
25
vendor/gopkg.in/mail.v2/mime_go14.go
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// +build !go1.5
|
||||
|
||||
package mail
|
||||
|
||||
import "gopkg.in/alexcesaro/quotedprintable.v3"
|
||||
|
||||
var newQPWriter = quotedprintable.NewWriter
|
||||
|
||||
type mimeEncoder struct {
|
||||
quotedprintable.WordEncoder
|
||||
}
|
||||
|
||||
var (
|
||||
bEncoding = mimeEncoder{quotedprintable.BEncoding}
|
||||
qEncoding = mimeEncoder{quotedprintable.QEncoding}
|
||||
lastIndexByte = func(s string, c byte) int {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
|
||||
if s[i] == c {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
)
|
||||
116
vendor/gopkg.in/mail.v2/send.go
generated
vendored
Normal file
116
vendor/gopkg.in/mail.v2/send.go
generated
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
stdmail "net/mail"
|
||||
)
|
||||
|
||||
// Sender is the interface that wraps the Send method.
|
||||
//
|
||||
// Send sends an email to the given addresses.
|
||||
type Sender interface {
|
||||
Send(from string, to []string, msg io.WriterTo) error
|
||||
}
|
||||
|
||||
// SendCloser is the interface that groups the Send and Close methods.
|
||||
type SendCloser interface {
|
||||
Sender
|
||||
Close() error
|
||||
}
|
||||
|
||||
// A SendFunc is a function that sends emails to the given addresses.
|
||||
//
|
||||
// The SendFunc type is an adapter to allow the use of ordinary functions as
|
||||
// email senders. If f is a function with the appropriate signature, SendFunc(f)
|
||||
// is a Sender object that calls f.
|
||||
type SendFunc func(from string, to []string, msg io.WriterTo) error
|
||||
|
||||
// Send calls f(from, to, msg).
|
||||
func (f SendFunc) Send(from string, to []string, msg io.WriterTo) error {
|
||||
return f(from, to, msg)
|
||||
}
|
||||
|
||||
// Send sends emails using the given Sender.
|
||||
func Send(s Sender, msg ...*Message) error {
|
||||
for i, m := range msg {
|
||||
if err := send(s, m); err != nil {
|
||||
return &SendError{Cause: err, Index: uint(i)}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func send(s Sender, m *Message) error {
|
||||
from, err := m.getFrom()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
to, err := m.getRecipients()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.Send(from, to, m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) getFrom() (string, error) {
|
||||
from := m.header["Sender"]
|
||||
if len(from) == 0 {
|
||||
from = m.header["From"]
|
||||
if len(from) == 0 {
|
||||
return "", errors.New(`gomail: invalid message, "From" field is absent`)
|
||||
}
|
||||
}
|
||||
|
||||
return parseAddress(from[0])
|
||||
}
|
||||
|
||||
func (m *Message) getRecipients() ([]string, error) {
|
||||
n := 0
|
||||
for _, field := range []string{"To", "Cc", "Bcc"} {
|
||||
if addresses, ok := m.header[field]; ok {
|
||||
n += len(addresses)
|
||||
}
|
||||
}
|
||||
list := make([]string, 0, n)
|
||||
|
||||
for _, field := range []string{"To", "Cc", "Bcc"} {
|
||||
if addresses, ok := m.header[field]; ok {
|
||||
for _, a := range addresses {
|
||||
addr, err := parseAddress(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = addAddress(list, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func addAddress(list []string, addr string) []string {
|
||||
for _, a := range list {
|
||||
if addr == a {
|
||||
return list
|
||||
}
|
||||
}
|
||||
|
||||
return append(list, addr)
|
||||
}
|
||||
|
||||
func parseAddress(field string) (string, error) {
|
||||
addr, err := stdmail.ParseAddress(field)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("gomail: invalid address %q: %v", field, err)
|
||||
}
|
||||
return addr.Address, nil
|
||||
}
|
||||
292
vendor/gopkg.in/mail.v2/smtp.go
generated
vendored
Normal file
292
vendor/gopkg.in/mail.v2/smtp.go
generated
vendored
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Dialer is a dialer to an SMTP server.
|
||||
type Dialer struct {
|
||||
// Host represents the host of the SMTP server.
|
||||
Host string
|
||||
// Port represents the port of the SMTP server.
|
||||
Port int
|
||||
// Username is the username to use to authenticate to the SMTP server.
|
||||
Username string
|
||||
// Password is the password to use to authenticate to the SMTP server.
|
||||
Password string
|
||||
// Auth represents the authentication mechanism used to authenticate to the
|
||||
// SMTP server.
|
||||
Auth smtp.Auth
|
||||
// SSL defines whether an SSL connection is used. It should be false in
|
||||
// most cases since the authentication mechanism should use the STARTTLS
|
||||
// extension instead.
|
||||
SSL bool
|
||||
// TLSConfig represents the TLS configuration used for the TLS (when the
|
||||
// STARTTLS extension is used) or SSL connection.
|
||||
TLSConfig *tls.Config
|
||||
// StartTLSPolicy represents the TLS security level required to
|
||||
// communicate with the SMTP server.
|
||||
//
|
||||
// This defaults to OpportunisticStartTLS for backwards compatibility,
|
||||
// but we recommend MandatoryStartTLS for all modern SMTP servers.
|
||||
//
|
||||
// This option has no effect if SSL is set to true.
|
||||
StartTLSPolicy StartTLSPolicy
|
||||
// LocalName is the hostname sent to the SMTP server with the HELO command.
|
||||
// By default, "localhost" is sent.
|
||||
LocalName string
|
||||
// Timeout to use for read/write operations. Defaults to 10 seconds, can
|
||||
// be set to 0 to disable timeouts.
|
||||
Timeout time.Duration
|
||||
// Whether we should retry mailing if the connection returned an error,
|
||||
// defaults to true.
|
||||
RetryFailure bool
|
||||
}
|
||||
|
||||
// NewDialer returns a new SMTP Dialer. The given parameters are used to connect
|
||||
// to the SMTP server.
|
||||
func NewDialer(host string, port int, username, password string) *Dialer {
|
||||
return &Dialer{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Username: username,
|
||||
Password: password,
|
||||
SSL: port == 465,
|
||||
Timeout: 10 * time.Second,
|
||||
RetryFailure: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewPlainDialer returns a new SMTP Dialer. The given parameters are used to
|
||||
// connect to the SMTP server.
|
||||
//
|
||||
// Deprecated: Use NewDialer instead.
|
||||
func NewPlainDialer(host string, port int, username, password string) *Dialer {
|
||||
return NewDialer(host, port, username, password)
|
||||
}
|
||||
|
||||
// NetDialTimeout specifies the DialTimeout function to establish a connection
|
||||
// to the SMTP server. This can be used to override dialing in the case that a
|
||||
// proxy or other special behavior is needed.
|
||||
var NetDialTimeout = net.DialTimeout
|
||||
|
||||
// Dial dials and authenticates to an SMTP server. The returned SendCloser
|
||||
// should be closed when done using it.
|
||||
func (d *Dialer) Dial() (SendCloser, error) {
|
||||
conn, err := NetDialTimeout("tcp", addr(d.Host, d.Port), d.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.SSL {
|
||||
conn = tlsClient(conn, d.tlsConfig())
|
||||
}
|
||||
|
||||
c, err := smtpNewClient(conn, d.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if d.Timeout > 0 {
|
||||
conn.SetDeadline(time.Now().Add(d.Timeout))
|
||||
}
|
||||
|
||||
if d.LocalName != "" {
|
||||
if err := c.Hello(d.LocalName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !d.SSL && d.StartTLSPolicy != NoStartTLS {
|
||||
ok, _ := c.Extension("STARTTLS")
|
||||
if !ok && d.StartTLSPolicy == MandatoryStartTLS {
|
||||
err := StartTLSUnsupportedError{
|
||||
Policy: d.StartTLSPolicy}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ok {
|
||||
if err := c.StartTLS(d.tlsConfig()); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if d.Auth == nil && d.Username != "" {
|
||||
if ok, auths := c.Extension("AUTH"); ok {
|
||||
if strings.Contains(auths, "CRAM-MD5") {
|
||||
d.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)
|
||||
} else if strings.Contains(auths, "LOGIN") &&
|
||||
!strings.Contains(auths, "PLAIN") {
|
||||
d.Auth = &loginAuth{
|
||||
username: d.Username,
|
||||
password: d.Password,
|
||||
host: d.Host,
|
||||
}
|
||||
} else {
|
||||
d.Auth = smtp.PlainAuth("", d.Username, d.Password, d.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if d.Auth != nil {
|
||||
if err = c.Auth(d.Auth); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &smtpSender{c, conn, d}, nil
|
||||
}
|
||||
|
||||
func (d *Dialer) tlsConfig() *tls.Config {
|
||||
if d.TLSConfig == nil {
|
||||
return &tls.Config{ServerName: d.Host}
|
||||
}
|
||||
return d.TLSConfig
|
||||
}
|
||||
|
||||
// StartTLSPolicy constants are valid values for Dialer.StartTLSPolicy.
|
||||
type StartTLSPolicy int
|
||||
|
||||
const (
|
||||
// OpportunisticStartTLS means that SMTP transactions are encrypted if
|
||||
// STARTTLS is supported by the SMTP server. Otherwise, messages are
|
||||
// sent in the clear. This is the default setting.
|
||||
OpportunisticStartTLS StartTLSPolicy = iota
|
||||
// MandatoryStartTLS means that SMTP transactions must be encrypted.
|
||||
// SMTP transactions are aborted unless STARTTLS is supported by the
|
||||
// SMTP server.
|
||||
MandatoryStartTLS
|
||||
// NoStartTLS means encryption is disabled and messages are sent in the
|
||||
// clear.
|
||||
NoStartTLS = -1
|
||||
)
|
||||
|
||||
func (policy *StartTLSPolicy) String() string {
|
||||
switch *policy {
|
||||
case OpportunisticStartTLS:
|
||||
return "OpportunisticStartTLS"
|
||||
case MandatoryStartTLS:
|
||||
return "MandatoryStartTLS"
|
||||
case NoStartTLS:
|
||||
return "NoStartTLS"
|
||||
default:
|
||||
return fmt.Sprintf("StartTLSPolicy:%v", *policy)
|
||||
}
|
||||
}
|
||||
|
||||
// StartTLSUnsupportedError is returned by Dial when connecting to an SMTP
|
||||
// server that does not support STARTTLS.
|
||||
type StartTLSUnsupportedError struct {
|
||||
Policy StartTLSPolicy
|
||||
}
|
||||
|
||||
func (e StartTLSUnsupportedError) Error() string {
|
||||
return "gomail: " + e.Policy.String() + " required, but " +
|
||||
"SMTP server does not support STARTTLS"
|
||||
}
|
||||
|
||||
func addr(host string, port int) string {
|
||||
return fmt.Sprintf("%s:%d", host, port)
|
||||
}
|
||||
|
||||
// DialAndSend opens a connection to the SMTP server, sends the given emails and
|
||||
// closes the connection.
|
||||
func (d *Dialer) DialAndSend(m ...*Message) error {
|
||||
s, err := d.Dial()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
return Send(s, m...)
|
||||
}
|
||||
|
||||
type smtpSender struct {
|
||||
smtpClient
|
||||
conn net.Conn
|
||||
d *Dialer
|
||||
}
|
||||
|
||||
func (c *smtpSender) retryError(err error) bool {
|
||||
if !c.d.RetryFailure {
|
||||
return false
|
||||
}
|
||||
|
||||
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
|
||||
return true
|
||||
}
|
||||
|
||||
return err == io.EOF
|
||||
}
|
||||
|
||||
func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
|
||||
if c.d.Timeout > 0 {
|
||||
c.conn.SetDeadline(time.Now().Add(c.d.Timeout))
|
||||
}
|
||||
|
||||
if err := c.Mail(from); err != nil {
|
||||
if c.retryError(err) {
|
||||
// This is probably due to a timeout, so reconnect and try again.
|
||||
sc, derr := c.d.Dial()
|
||||
if derr == nil {
|
||||
if s, ok := sc.(*smtpSender); ok {
|
||||
*c = *s
|
||||
return c.Send(from, to, msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
for _, addr := range to {
|
||||
if err := c.Rcpt(addr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
w, err := c.Data()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = msg.WriteTo(w); err != nil {
|
||||
w.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Close()
|
||||
}
|
||||
|
||||
func (c *smtpSender) Close() error {
|
||||
return c.Quit()
|
||||
}
|
||||
|
||||
// Stubbed out for tests.
|
||||
var (
|
||||
tlsClient = tls.Client
|
||||
smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
|
||||
return smtp.NewClient(conn, host)
|
||||
}
|
||||
)
|
||||
|
||||
type smtpClient interface {
|
||||
Hello(string) error
|
||||
Extension(string) (bool, string)
|
||||
StartTLS(*tls.Config) error
|
||||
Auth(smtp.Auth) error
|
||||
Mail(string) error
|
||||
Rcpt(string) error
|
||||
Data() (io.WriteCloser, error)
|
||||
Quit() error
|
||||
Close() error
|
||||
}
|
||||
313
vendor/gopkg.in/mail.v2/writeto.go
generated
vendored
Normal file
313
vendor/gopkg.in/mail.v2/writeto.go
generated
vendored
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// WriteTo implements io.WriterTo. It dumps the whole message into w.
|
||||
func (m *Message) WriteTo(w io.Writer) (int64, error) {
|
||||
mw := &messageWriter{w: w}
|
||||
mw.writeMessage(m)
|
||||
return mw.n, mw.err
|
||||
}
|
||||
|
||||
func (w *messageWriter) writeMessage(m *Message) {
|
||||
if _, ok := m.header["MIME-Version"]; !ok {
|
||||
w.writeString("MIME-Version: 1.0\r\n")
|
||||
}
|
||||
if _, ok := m.header["Date"]; !ok {
|
||||
w.writeHeader("Date", m.FormatDate(now()))
|
||||
}
|
||||
w.writeHeaders(m.header)
|
||||
|
||||
if m.hasMixedPart() {
|
||||
w.openMultipart("mixed", m.boundary)
|
||||
}
|
||||
|
||||
if m.hasRelatedPart() {
|
||||
w.openMultipart("related", m.boundary)
|
||||
}
|
||||
|
||||
if m.hasAlternativePart() {
|
||||
w.openMultipart("alternative", m.boundary)
|
||||
}
|
||||
for _, part := range m.parts {
|
||||
w.writePart(part, m.charset)
|
||||
}
|
||||
if m.hasAlternativePart() {
|
||||
w.closeMultipart()
|
||||
}
|
||||
|
||||
w.addFiles(m.embedded, false)
|
||||
if m.hasRelatedPart() {
|
||||
w.closeMultipart()
|
||||
}
|
||||
|
||||
w.addFiles(m.attachments, true)
|
||||
if m.hasMixedPart() {
|
||||
w.closeMultipart()
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Message) hasMixedPart() bool {
|
||||
return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
|
||||
}
|
||||
|
||||
func (m *Message) hasRelatedPart() bool {
|
||||
return (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1
|
||||
}
|
||||
|
||||
func (m *Message) hasAlternativePart() bool {
|
||||
return len(m.parts) > 1
|
||||
}
|
||||
|
||||
type messageWriter struct {
|
||||
w io.Writer
|
||||
n int64
|
||||
writers [3]*multipart.Writer
|
||||
partWriter io.Writer
|
||||
depth uint8
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *messageWriter) openMultipart(mimeType, boundary string) {
|
||||
mw := multipart.NewWriter(w)
|
||||
if boundary != "" {
|
||||
mw.SetBoundary(boundary)
|
||||
}
|
||||
contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()
|
||||
w.writers[w.depth] = mw
|
||||
|
||||
if w.depth == 0 {
|
||||
w.writeHeader("Content-Type", contentType)
|
||||
w.writeString("\r\n")
|
||||
} else {
|
||||
w.createPart(map[string][]string{
|
||||
"Content-Type": {contentType},
|
||||
})
|
||||
}
|
||||
w.depth++
|
||||
}
|
||||
|
||||
func (w *messageWriter) createPart(h map[string][]string) {
|
||||
w.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)
|
||||
}
|
||||
|
||||
func (w *messageWriter) closeMultipart() {
|
||||
if w.depth > 0 {
|
||||
w.writers[w.depth-1].Close()
|
||||
w.depth--
|
||||
}
|
||||
}
|
||||
|
||||
func (w *messageWriter) writePart(p *part, charset string) {
|
||||
w.writeHeaders(map[string][]string{
|
||||
"Content-Type": {p.contentType + "; charset=" + charset},
|
||||
"Content-Transfer-Encoding": {string(p.encoding)},
|
||||
})
|
||||
w.writeBody(p.copier, p.encoding)
|
||||
}
|
||||
|
||||
func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
|
||||
for _, f := range files {
|
||||
if _, ok := f.Header["Content-Type"]; !ok {
|
||||
mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
|
||||
if mediaType == "" {
|
||||
mediaType = "application/octet-stream"
|
||||
}
|
||||
f.setHeader("Content-Type", mediaType+`; name="`+f.Name+`"`)
|
||||
}
|
||||
|
||||
if _, ok := f.Header["Content-Transfer-Encoding"]; !ok {
|
||||
f.setHeader("Content-Transfer-Encoding", string(Base64))
|
||||
}
|
||||
|
||||
if _, ok := f.Header["Content-Disposition"]; !ok {
|
||||
var disp string
|
||||
if isAttachment {
|
||||
disp = "attachment"
|
||||
} else {
|
||||
disp = "inline"
|
||||
}
|
||||
f.setHeader("Content-Disposition", disp+`; filename="`+f.Name+`"`)
|
||||
}
|
||||
|
||||
if !isAttachment {
|
||||
if _, ok := f.Header["Content-ID"]; !ok {
|
||||
f.setHeader("Content-ID", "<"+f.Name+">")
|
||||
}
|
||||
}
|
||||
w.writeHeaders(f.Header)
|
||||
w.writeBody(f.CopyFunc, Base64)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *messageWriter) Write(p []byte) (int, error) {
|
||||
if w.err != nil {
|
||||
return 0, errors.New("gomail: cannot write as writer is in error")
|
||||
}
|
||||
|
||||
var n int
|
||||
n, w.err = w.w.Write(p)
|
||||
w.n += int64(n)
|
||||
return n, w.err
|
||||
}
|
||||
|
||||
func (w *messageWriter) writeString(s string) {
|
||||
if w.err != nil { // do nothing when in error
|
||||
return
|
||||
}
|
||||
var n int
|
||||
n, w.err = io.WriteString(w.w, s)
|
||||
w.n += int64(n)
|
||||
}
|
||||
|
||||
func (w *messageWriter) writeHeader(k string, v ...string) {
|
||||
w.writeString(k)
|
||||
if len(v) == 0 {
|
||||
w.writeString(":\r\n")
|
||||
return
|
||||
}
|
||||
w.writeString(": ")
|
||||
|
||||
// Max header line length is 78 characters in RFC 5322 and 76 characters
|
||||
// in RFC 2047. So for the sake of simplicity we use the 76 characters
|
||||
// limit.
|
||||
charsLeft := 76 - len(k) - len(": ")
|
||||
|
||||
for i, s := range v {
|
||||
// If the line is already too long, insert a newline right away.
|
||||
if charsLeft < 1 {
|
||||
if i == 0 {
|
||||
w.writeString("\r\n ")
|
||||
} else {
|
||||
w.writeString(",\r\n ")
|
||||
}
|
||||
charsLeft = 75
|
||||
} else if i != 0 {
|
||||
w.writeString(", ")
|
||||
charsLeft -= 2
|
||||
}
|
||||
|
||||
// While the header content is too long, fold it by inserting a newline.
|
||||
for len(s) > charsLeft {
|
||||
s = w.writeLine(s, charsLeft)
|
||||
charsLeft = 75
|
||||
}
|
||||
w.writeString(s)
|
||||
if i := lastIndexByte(s, '\n'); i != -1 {
|
||||
charsLeft = 75 - (len(s) - i - 1)
|
||||
} else {
|
||||
charsLeft -= len(s)
|
||||
}
|
||||
}
|
||||
w.writeString("\r\n")
|
||||
}
|
||||
|
||||
func (w *messageWriter) writeLine(s string, charsLeft int) string {
|
||||
// If there is already a newline before the limit. Write the line.
|
||||
if i := strings.IndexByte(s, '\n'); i != -1 && i < charsLeft {
|
||||
w.writeString(s[:i+1])
|
||||
return s[i+1:]
|
||||
}
|
||||
|
||||
for i := charsLeft - 1; i >= 0; i-- {
|
||||
if s[i] == ' ' {
|
||||
w.writeString(s[:i])
|
||||
w.writeString("\r\n ")
|
||||
return s[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// We could not insert a newline cleanly so look for a space or a newline
|
||||
// even if it is after the limit.
|
||||
for i := 75; i < len(s); i++ {
|
||||
if s[i] == ' ' {
|
||||
w.writeString(s[:i])
|
||||
w.writeString("\r\n ")
|
||||
return s[i+1:]
|
||||
}
|
||||
if s[i] == '\n' {
|
||||
w.writeString(s[:i+1])
|
||||
return s[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// Too bad, no space or newline in the whole string. Just write everything.
|
||||
w.writeString(s)
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w *messageWriter) writeHeaders(h map[string][]string) {
|
||||
if w.depth == 0 {
|
||||
for k, v := range h {
|
||||
if k != "Bcc" {
|
||||
w.writeHeader(k, v...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
w.createPart(h)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *messageWriter) writeBody(f func(io.Writer) error, enc Encoding) {
|
||||
var subWriter io.Writer
|
||||
if w.depth == 0 {
|
||||
w.writeString("\r\n")
|
||||
subWriter = w.w
|
||||
} else {
|
||||
subWriter = w.partWriter
|
||||
}
|
||||
|
||||
if enc == Base64 {
|
||||
wc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))
|
||||
w.err = f(wc)
|
||||
wc.Close()
|
||||
} else if enc == Unencoded {
|
||||
w.err = f(subWriter)
|
||||
} else {
|
||||
wc := newQPWriter(subWriter)
|
||||
w.err = f(wc)
|
||||
wc.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// As required by RFC 2045, 6.7. (page 21) for quoted-printable, and
|
||||
// RFC 2045, 6.8. (page 25) for base64.
|
||||
const maxLineLen = 76
|
||||
|
||||
// base64LineWriter limits text encoded in base64 to 76 characters per line
|
||||
type base64LineWriter struct {
|
||||
w io.Writer
|
||||
lineLen int
|
||||
}
|
||||
|
||||
func newBase64LineWriter(w io.Writer) *base64LineWriter {
|
||||
return &base64LineWriter{w: w}
|
||||
}
|
||||
|
||||
func (w *base64LineWriter) Write(p []byte) (int, error) {
|
||||
n := 0
|
||||
for len(p)+w.lineLen > maxLineLen {
|
||||
w.w.Write(p[:maxLineLen-w.lineLen])
|
||||
w.w.Write([]byte("\r\n"))
|
||||
p = p[maxLineLen-w.lineLen:]
|
||||
n += maxLineLen - w.lineLen
|
||||
w.lineLen = 0
|
||||
}
|
||||
|
||||
w.w.Write(p)
|
||||
w.lineLen += len(p)
|
||||
|
||||
return n + len(p), nil
|
||||
}
|
||||
|
||||
// Stubbed out for testing.
|
||||
var now = time.Now
|
||||
9
vendor/modules.txt
vendored
9
vendor/modules.txt
vendored
|
|
@ -20,9 +20,18 @@ github.com/rs/zerolog/log
|
|||
## explicit; go 1.17
|
||||
github.com/stretchr/testify/assert
|
||||
github.com/stretchr/testify/assert/yaml
|
||||
# github.com/urfave/cli/v3 v3.4.1
|
||||
## explicit; go 1.22
|
||||
github.com/urfave/cli/v3
|
||||
# golang.org/x/sys v0.12.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/sys/unix
|
||||
# gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc
|
||||
## explicit
|
||||
gopkg.in/alexcesaro/quotedprintable.v3
|
||||
# gopkg.in/mail.v2 v2.3.1
|
||||
## explicit
|
||||
gopkg.in/mail.v2
|
||||
# gopkg.in/yaml.v2 v2.4.0
|
||||
## explicit; go 1.15
|
||||
gopkg.in/yaml.v2
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue