Compare commits
5 commits
ced5a4e6eb
...
22c654c9b1
Author | SHA1 | Date | |
---|---|---|---|
22c654c9b1 | |||
53e389ca3c | |||
f96899c9c9 | |||
43b6557178 | |||
b558c5adff |
7 changed files with 218 additions and 12 deletions
|
@ -24,6 +24,7 @@ the `main` branch mirrored to the following forges:
|
||||||
- **[Getting started guide](docs/getting_started.md)**: A guide to help you get started on using Enbas.
|
- **[Getting started guide](docs/getting_started.md)**: A guide to help you get started on using Enbas.
|
||||||
- **[Configuration reference](docs/configuration.md)**: The configuration reference documentation.
|
- **[Configuration reference](docs/configuration.md)**: The configuration reference documentation.
|
||||||
- **[User manual](docs/manual.md)**: The user manual.
|
- **[User manual](docs/manual.md)**: The user manual.
|
||||||
|
- **[Tips and Tricks](docs/tips_and_tricks.md)**: Additional tips and tricks.
|
||||||
|
|
||||||
### Licensing
|
### Licensing
|
||||||
|
|
||||||
|
|
|
@ -269,16 +269,21 @@ enbas show --type blocked
|
||||||
|
|
||||||
### Mute an account
|
### Mute an account
|
||||||
|
|
||||||
```
|
- Mute an account indefinitely.
|
||||||
enbas mute --type account --account-name @name@example.social --mute-notifications --mute-duration="1h"
|
```
|
||||||
```
|
enbas mute --type account --account-name @name@example.social --mute-notifications"
|
||||||
|
```
|
||||||
|
- Mute an account for 1 and a half hours.
|
||||||
|
```
|
||||||
|
enbas mute --type account --account-name @name@example.social --mute-notifications --mute-duration="1 hour and 30 minutes"
|
||||||
|
```
|
||||||
|
|
||||||
| flag | type | required | description | default |
|
| flag | type | required | description | default |
|
||||||
|------|------|----------|-------------|---------|
|
|------|------|----------|-------------|---------|
|
||||||
| `type` | string | true | The resource you want to mute.<br>Here this should be `account`. | |
|
| `type` | string | true | The resource you want to mute.<br>Here this should be `account`. | |
|
||||||
| `account-name` | string | true | The name of the account to mute. | |
|
| `account-name` | string | true | The name of the account to mute. | |
|
||||||
| `mute-notifications` | boolean | false | Set to `true` to mute notifications as well as statuses. | false |
|
| `mute-notifications` | boolean | false | Set to `true` to mute notifications as well as statuses. | false |
|
||||||
| `mute-duration` | string | false | Specify how long the account should be muted for.<br>Set to `0s` to mute indefinitely | 0s (indefinitely). |
|
| `mute-duration` | [time duration value](tips_and_tricks.md#the-time-duration-value) | false | Specify how long the account should be muted for.<br>Set to `0 seconds` to mute indefinitely | 0 seconds (indefinitely). |
|
||||||
|
|
||||||
### Unmute an account
|
### Unmute an account
|
||||||
|
|
||||||
|
@ -476,7 +481,7 @@ Creates a new status.
|
||||||
--content "The age-old question: which text editor do you prefer?" \
|
--content "The age-old question: which text editor do you prefer?" \
|
||||||
--add-poll \
|
--add-poll \
|
||||||
--poll-allows-multiple-choices=false \
|
--poll-allows-multiple-choices=false \
|
||||||
--poll-expires-in 168h \
|
--poll-expires-in "7 days" \
|
||||||
--poll-option "emacs" \
|
--poll-option "emacs" \
|
||||||
--poll-option "vim/neovim" \
|
--poll-option "vim/neovim" \
|
||||||
--poll-option "nano" \
|
--poll-option "nano" \
|
||||||
|
@ -528,7 +533,7 @@ Additional flags for polls.
|
||||||
| `poll-allows-multiple-choices` | boolean | false | Set to `true` to allow users to make multiple choices. | false |
|
| `poll-allows-multiple-choices` | boolean | false | Set to `true` to allow users to make multiple choices. | false |
|
||||||
| `poll-hides-vote-counts` | boolean | false | Set to `true` to hide the vote count until the poll is closed. | false |
|
| `poll-hides-vote-counts` | boolean | false | Set to `true` to hide the vote count until the poll is closed. | false |
|
||||||
| `poll-option` | string | true | An option in the poll. Use this flag multiple times to set multiple options. | |
|
| `poll-option` | string | true | An option in the poll. Use this flag multiple times to set multiple options. | |
|
||||||
| `poll-expires-in` | string | false | The duration in which the poll is open for. | |
|
| `poll-expires-in` | [time duration value](tips_and_tricks.md#the-time-duration-value) | false | The duration in which the poll is open for. | |
|
||||||
|
|
||||||
### Delete a status
|
### Delete a status
|
||||||
|
|
||||||
|
|
24
docs/tips_and_tricks.md
Normal file
24
docs/tips_and_tricks.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Tips and Tricks
|
||||||
|
|
||||||
|
## The time duration value
|
||||||
|
|
||||||
|
The time duration value is a custom [flag value](https://pkg.go.dev/flag#Value) that converts a string input into a duration of time.
|
||||||
|
A typical string input would be in the form of something like `"3 days, 12 hours and 39 minutes"`.
|
||||||
|
The value can convert units in days, hours, minutes and seconds.
|
||||||
|
|
||||||
|
To ensure that your string input is converted correctly there are simple rules to follow.
|
||||||
|
|
||||||
|
- The input must be wrapped in quotes.
|
||||||
|
- Use `day` or `days` to convert the number of days.
|
||||||
|
- Use `hour` or `hours` to convert the number of hours.
|
||||||
|
- Use `minute` or `minutes` to convert the number of minutes.
|
||||||
|
- Use `second` or `seconds` to convert the number of seconds.
|
||||||
|
- There must be at least one space between the number and the unit of time.<br>
|
||||||
|
E.g. `"7 days"` is valid, but `"7days"` is invalid.
|
||||||
|
|
||||||
|
### Example valid string inputs
|
||||||
|
|
||||||
|
- `"3 days"`
|
||||||
|
- `"6 hours, 45 minutes and 1 second"`
|
||||||
|
- `"1 day, 15 hours 31 minutes and 12 seconds"`
|
||||||
|
- `"(7 days) (1 hour) (21 minutes) (35 seconds)"`
|
|
@ -34,3 +34,5 @@ func (b *BoolPtrValue) Set(value string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BoolPtrValue) IsBoolFlag() bool { return true }
|
||||||
|
|
65
internal/flag/boolptrvalue_test.go
Normal file
65
internal/flag/boolptrvalue_test.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package flag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
internalFlag "codeflow.dananglin.me.uk/apollo/enbas/internal/flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBoolPtrValue(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "True",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "false",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
value := internalFlag.NewBoolPtrValue()
|
||||||
|
|
||||||
|
for _, test := range slices.All(tests) {
|
||||||
|
if err := value.Set(test.input); err != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"Unable to parse %s as a BoolPtrValue: %v",
|
||||||
|
test.input,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := *value.Value
|
||||||
|
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf(
|
||||||
|
"Unexpected bool parsed from %s: want %t, got %t",
|
||||||
|
test.input,
|
||||||
|
test.want,
|
||||||
|
got,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
t.Logf(
|
||||||
|
"Expected bool parsed from %s: got %t",
|
||||||
|
test.input,
|
||||||
|
got,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNilBoolPtrValue(t *testing.T) {
|
||||||
|
value := internalFlag.NewBoolPtrValue()
|
||||||
|
want := "NOT SET"
|
||||||
|
got := value.String()
|
||||||
|
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Unexpected string returned from the nil value; want %s, got %s", want, got)
|
||||||
|
} else {
|
||||||
|
t.Logf("Expected string returned from the nil value; got %s", got)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,16 +2,21 @@ package flag
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const timeDurationRegexPattern string = `[0-9]{1,4}\s+(days?|hours?|minutes?|seconds?)`
|
||||||
|
|
||||||
type TimeDurationValue struct {
|
type TimeDurationValue struct {
|
||||||
Duration time.Duration
|
Duration time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTimeDurationValue() TimeDurationValue {
|
func NewTimeDurationValue() TimeDurationValue {
|
||||||
return TimeDurationValue{
|
return TimeDurationValue{
|
||||||
Duration: 0 * time.Second,
|
Duration: time.Duration(0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,13 +24,56 @@ func (v TimeDurationValue) String() string {
|
||||||
return v.Duration.String()
|
return v.Duration.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *TimeDurationValue) Set(text string) error {
|
func (v *TimeDurationValue) Set(value string) error {
|
||||||
duration, err := time.ParseDuration(text)
|
pattern := regexp.MustCompile(timeDurationRegexPattern)
|
||||||
if err != nil {
|
matches := pattern.FindAllString(value, -1)
|
||||||
return fmt.Errorf("unable to parse the value as time duration: %w", err)
|
|
||||||
|
days, hours, minutes, seconds := 0, 0, 0, 0
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
for ind := range len(matches) {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(matches[ind], "day"):
|
||||||
|
days, err = parseInt(matches[ind])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse the number of days from %s: %w", matches[ind], err)
|
||||||
|
}
|
||||||
|
case strings.Contains(matches[ind], "hour"):
|
||||||
|
hours, err = parseInt(matches[ind])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse the number of hours from %s: %w", matches[ind], err)
|
||||||
|
}
|
||||||
|
case strings.Contains(matches[ind], "minute"):
|
||||||
|
minutes, err = parseInt(matches[ind])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse the number of minutes from %s: %w", matches[ind], err)
|
||||||
|
}
|
||||||
|
case strings.Contains(matches[ind], "second"):
|
||||||
|
seconds, err = parseInt(matches[ind])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to parse the number of seconds from %s: %w", matches[ind], err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Duration = duration
|
durationValue := (days * 86400) + (hours * 3600) + (minutes * 60) + seconds
|
||||||
|
|
||||||
|
v.Duration = time.Duration(durationValue) * time.Second
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseInt(text string) (int, error) {
|
||||||
|
split := strings.SplitN(text, " ", 2)
|
||||||
|
if len(split) != 2 {
|
||||||
|
return 0, fmt.Errorf("unexpected number of split for %s: want 2, got %d", text, len(split))
|
||||||
|
}
|
||||||
|
|
||||||
|
output, err := strconv.Atoi(split[0])
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("unable to convert %s to an integer: %w", text, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return output, nil
|
||||||
|
}
|
||||||
|
|
61
internal/flag/timedurationvalue_test.go
Normal file
61
internal/flag/timedurationvalue_test.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package flag_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
internalFlag "codeflow.dananglin.me.uk/apollo/enbas/internal/flag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimeDurationValue(t *testing.T) {
|
||||||
|
parsingTests := []struct {
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "1 day",
|
||||||
|
want: "24h0m0s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "3 days, 5 hours, 39 minutes and 6 seconds",
|
||||||
|
want: "77h39m6s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1 minute and 30 seconds",
|
||||||
|
want: "1m30s",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "(7 seconds) (21 hours) (41 days)",
|
||||||
|
want: "1005h0m7s",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
value := internalFlag.NewTimeDurationValue()
|
||||||
|
|
||||||
|
for _, test := range slices.All(parsingTests) {
|
||||||
|
if err := value.Set(test.input); err != nil {
|
||||||
|
t.Fatalf(
|
||||||
|
"Unable to parse %s into a TimeDurationValue: %v",
|
||||||
|
test.input,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := value.String()
|
||||||
|
|
||||||
|
if got != test.want {
|
||||||
|
t.Errorf(
|
||||||
|
"Unexpected duration parsed from %s: want %s, got %s",
|
||||||
|
test.input,
|
||||||
|
test.want,
|
||||||
|
got,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
t.Logf(
|
||||||
|
"Expected duration parsed from %s: got %s",
|
||||||
|
test.input,
|
||||||
|
got,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue