-
Notifications
You must be signed in to change notification settings - Fork 1
/
cli.go
215 lines (177 loc) 路 5.22 KB
/
cli.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package cli
import (
"fmt"
"github.com/altdesktop/playerbm/internal/model"
"github.com/kballard/go-shellquote"
"log"
"strings"
)
type PbmCli struct {
PlayerCmd string
ListBookmarksFlag bool
ListPlayersFlag bool
HelpFlag bool
VersionFlag bool
ResumeFlag bool
ResumeUrl *model.XesamUrl
SaveFlag bool
SavePlayers string
DeleteFlag bool
DeleteUrl *model.XesamUrl
}
const HelpString = `playerbm [OPTION鈥 PLAYER_COMMAND
Description:
playerbm is a utility that saves your place when you exit the player or
change the track and automatically resumes from where you left off when you
open it again.
Pass the command to open your media player as PLAYER_COMMAND and playerbm
will connect to the player over the MPRIS DBus Specification and begin
managing bookmarks.
Example:
playerbm player ~/audiobooks/war-and-peace.mp3
Listen for awhile and close the player. When you open the player again with
playerbm, it will seek to your last position.
Options:
-l, --list-bookmarks List all bookmarks and exit.
-L, --list-players List all running players that can be controlled.
-r, --resume=[URL] Launch a player and resume playing URL from the last
saved bookmark and begin managing bookmarks. (default:
file of the last saved bookmark)
-s, --save=[PLAYER] Save bookmarks for the running players in a comma
separated list. (default: all running players)
-d, --delete={URL} Delete the bookmark for the given url.
-h, --help Show help.
-v, --version Print the version.` + "\n"
const VersionString = "v0.0.1\n"
type BoolFlag struct {
Short string
Long string
Value *bool
}
type StringFlag struct {
Short string
Long string
Present *bool
ArgValuePresent bool
ArgValue *string
}
type CliError struct {
err string
}
func (e *CliError) Error() string {
return e.err
}
func newCliError(format string, args ...interface{}) *CliError {
return &CliError{err: fmt.Sprintf(format, args...)}
}
func parseBoolFlag(arg string, flag BoolFlag) (bool, error) {
if arg == flag.Short || arg == flag.Long {
*flag.Value = true
return true, nil
}
if strings.HasPrefix(arg, flag.Short+"=") || strings.HasPrefix(arg, flag.Long+"=") {
return false, newCliError("no argument expected for %s, %s flag", flag.Short, flag.Long)
}
return false, nil
}
func parseStringFlag(arg string, flag StringFlag) (bool, bool, error) {
if arg == flag.Short || arg == flag.Long {
*flag.Present = true
return true, false, nil
}
if strings.HasPrefix(arg, flag.Short+"=") || strings.HasPrefix(arg, flag.Long+"=") {
*flag.Present = true
*flag.ArgValue = strings.Split(arg, "=")[1]
return true, true, nil
}
return false, false, nil
}
func ParseArgs(args []string) (*PbmCli, error) {
var err error
log.Printf("[DEBUG] parsing arguments: %v", args)
cli := PbmCli{}
if len(args) == 1 {
cli.HelpFlag = true
return &cli, nil
}
boolFlags := []BoolFlag{
BoolFlag{Short: "-v", Long: "--version", Value: &cli.VersionFlag},
BoolFlag{Short: "-h", Long: "--help", Value: &cli.HelpFlag},
BoolFlag{Short: "-l", Long: "--list-bookmarks", Value: &cli.ListBookmarksFlag},
BoolFlag{Short: "-L", Long: "--list-players", Value: &cli.ListPlayersFlag},
}
var resumeUrl string
var deleteUrl string
stringFlags := []StringFlag{
StringFlag{Short: "-s", Long: "--save", Present: &cli.SaveFlag, ArgValue: &cli.SavePlayers},
StringFlag{Short: "-r", Long: "--resume", Present: &cli.ResumeFlag, ArgValue: &resumeUrl},
StringFlag{Short: "-d", Long: "--delete", Present: &cli.DeleteFlag, ArgValue: &deleteUrl},
}
firstPlayerArg := -1
skipNext := true
for i, arg := range args {
if skipNext {
skipNext = false
continue
}
if strings.HasPrefix(arg, "-") {
var match bool
for _, flag := range boolFlags {
match, err = parseBoolFlag(arg, flag)
if err != nil {
return nil, err
}
if match {
break
}
}
if !match {
for _, flag := range stringFlags {
var argValuePresent bool
match, argValuePresent, err = parseStringFlag(arg, flag)
if err != nil {
return nil, err
}
if match {
if !argValuePresent {
// look ahead
if len(args) < i+2 || strings.HasPrefix(args[i+1], "-") {
break
}
*flag.ArgValue = args[i+1]
skipNext = true
}
break
}
}
}
if !match {
return nil, newCliError("unknown argument: %s", arg)
}
} else {
firstPlayerArg = i
break
}
}
if cli.ResumeFlag && len(resumeUrl) > 0 {
cli.ResumeUrl, err = model.ParseXesamUrl(resumeUrl)
if err != nil {
return nil, newCliError("could not parse url: %s", resumeUrl)
}
}
if cli.DeleteFlag {
if len(deleteUrl) == 0 {
return nil, newCliError("a URL argument is required for the delete flag")
}
cli.DeleteUrl, err = model.ParseXesamUrl(deleteUrl)
if err != nil {
return nil, newCliError("could not parse url: %s", deleteUrl)
}
}
if firstPlayerArg != -1 {
cli.PlayerCmd = shellquote.Join(args[firstPlayerArg:]...)
}
// TODO: argument validation
log.Printf("[DEBUG] args: %+v", cli)
return &cli, nil
}