Skip to content

Commit b9740e8

Browse files
authored
Merge pull request #6 from mutablelogic/v1
Updated go-client
2 parents c838737 + ca5643f commit b9740e8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2970
-693
lines changed

.github/workflows/merge.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
name: "Merge to main"
33
on:
4-
push:
4+
pull_request:
55
branches: [ main ]
66
jobs:
77
analyze:

cmd/cli/command.go

+36-6
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
package main
22

33
import (
4+
"flag"
45
"fmt"
5-
6-
"golang.org/x/exp/maps"
76
)
87

98
///////////////////////////////////////////////////////////////////////////////
109
// TYPES
1110

1211
type Client struct {
13-
ns string
14-
cmd []Command
12+
ns string
13+
description string
14+
cmd []Command
1515
}
1616

1717
type Command struct {
1818
Name string
1919
Description string
20+
Syntax string
2021
MinArgs int
2122
MaxArgs int
2223
Fn CommandFn
@@ -34,11 +35,11 @@ func Run(clients []Client, flags *Flags) error {
3435
}
3536

3637
if flags.NArg() == 0 {
37-
return fmt.Errorf("no command specified, available commands are: %q", maps.Keys(ns))
38+
return flag.ErrHelp
3839
}
3940
cmd, exists := ns[flags.Arg(0)]
4041
if !exists {
41-
return fmt.Errorf("unknown command: %q, available commands are: %q", flags.Arg(0), maps.Keys(ns))
42+
return flag.ErrHelp
4243
}
4344

4445
var fn CommandFn
@@ -68,3 +69,32 @@ func Run(clients []Client, flags *Flags) error {
6869
return fn()
6970
}
7071
}
72+
73+
func PrintCommands(flags *Flags, clients []Client) {
74+
for i, client := range clients {
75+
if len(client.cmd) == 0 {
76+
continue
77+
}
78+
if i > 0 {
79+
fmt.Fprintln(flags.Output())
80+
}
81+
fmt.Fprintf(flags.Output(), " %s:\n", client.ns)
82+
if client.description != "" {
83+
fmt.Fprintf(flags.Output(), " %s\n", client.description)
84+
}
85+
for _, cmd := range client.cmd {
86+
if cmd.MinArgs == 0 && cmd.MaxArgs == 0 {
87+
fmt.Fprintf(flags.Output(), " %s %s", flags.Name(), client.ns)
88+
} else {
89+
fmt.Fprintf(flags.Output(), " %s %s %s", flags.Name(), client.ns, cmd.Name)
90+
}
91+
if cmd.Syntax != "" {
92+
fmt.Fprintf(flags.Output(), " %s", cmd.Syntax)
93+
}
94+
if cmd.Description != "" {
95+
fmt.Fprintf(flags.Output(), "\n %s", cmd.Description)
96+
}
97+
fmt.Fprintln(flags.Output())
98+
}
99+
}
100+
}

cmd/cli/elevenlabs.go

+68-14
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
11
package main
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"os"
6+
"regexp"
7+
"strings"
78

89
// Packages
910
"github.com/mutablelogic/go-client/pkg/client"
1011
"github.com/mutablelogic/go-client/pkg/elevenlabs"
12+
13+
// Namespace imports
14+
. "github.com/djthorpe/go-errors"
15+
)
16+
17+
/////////////////////////////////////////////////////////////////////
18+
// GLOBALS
19+
20+
var (
21+
reVoiceId = regexp.MustCompile("^[a-zA-Z0-9]{20}$")
1122
)
1223

24+
/////////////////////////////////////////////////////////////////////
25+
// REGISTER FUNCTIONS
26+
1327
func ElevenlabsFlags(flags *Flags) {
1428
flags.String("elevenlabs-api-key", "${ELEVENLABS_API_KEY}", "ElevenLabs API key")
1529
flags.String("elevenlabs-voice", "", "Voice")
@@ -32,43 +46,60 @@ func ElevenlabsRegister(cmd []Client, opts []client.ClientOpt, flags *Flags) ([]
3246
cmd = append(cmd, Client{
3347
ns: "elevenlabs",
3448
cmd: []Command{
35-
{Name: "voices", Description: "Return registered voices", MinArgs: 2, MaxArgs: 2, Fn: ElevenlabsVoices(elevenlabs, flags)},
36-
{Name: "voice", Description: "Return a voice", MinArgs: 3, MaxArgs: 3, Fn: ElevenlabsVoice(elevenlabs, flags)},
37-
{Name: "tts", Description: "Text-to-speech", MinArgs: 3, MaxArgs: 3, Fn: ElevenlabsTextToSpeech(elevenlabs, flags)},
49+
{Name: "voices", Description: "Return registered voices", MinArgs: 2, MaxArgs: 2, Fn: elevenlabsVoices(elevenlabs, flags)},
50+
{Name: "voice", Description: "Return a voice", Syntax: "<voice>", MinArgs: 3, MaxArgs: 3, Fn: elevenlabsVoice(elevenlabs, flags)},
51+
{Name: "preview", Description: "Preview a voice", Syntax: "<voice>", MinArgs: 3, MaxArgs: 3, Fn: elevenlabsVoicePreview(elevenlabs, flags)},
52+
{Name: "say", Description: "Text-to-speech", Syntax: "<voice> <text>", MinArgs: 3, MaxArgs: 3, Fn: elevenlabsTextToSpeech(elevenlabs, flags)},
3853
},
3954
})
4055

4156
// Return success
4257
return cmd, nil
4358
}
4459

45-
func ElevenlabsVoices(client *elevenlabs.Client, flags *Flags) CommandFn {
60+
/////////////////////////////////////////////////////////////////////
61+
// API CALL FUNCTIONS
62+
63+
func elevenlabsVoices(client *elevenlabs.Client, flags *Flags) CommandFn {
4664
return func() error {
4765
if voices, err := client.Voices(); err != nil {
4866
return err
49-
} else if data, err := json.MarshalIndent(voices, "", " "); err != nil {
67+
} else {
68+
return flags.Write(voices)
69+
}
70+
}
71+
}
72+
73+
func elevenlabsVoice(client *elevenlabs.Client, flags *Flags) CommandFn {
74+
return func() error {
75+
voice, err := elevenlabsGetVoiceId(client, flags.Arg(2))
76+
if err != nil {
77+
return err
78+
} else if voice, err := client.Voice(voice); err != nil {
5079
return err
5180
} else {
52-
fmt.Printf("Voices: %s\n", data)
81+
return flags.Write(voice)
5382
}
54-
return nil
5583
}
5684
}
5785

58-
func ElevenlabsVoice(client *elevenlabs.Client, flags *Flags) CommandFn {
86+
func elevenlabsVoicePreview(client *elevenlabs.Client, flags *Flags) CommandFn {
5987
return func() error {
60-
if voice, err := client.Voice(flags.Arg(2)); err != nil {
88+
voice, err := elevenlabsGetVoiceId(client, flags.Arg(2))
89+
if err != nil {
6190
return err
62-
} else if data, err := json.MarshalIndent(voice, "", " "); err != nil {
91+
} else if voice, err := client.Voice(voice); err != nil {
6392
return err
93+
} else if voice.PreviewUrl == "" {
94+
return ErrNotFound.Withf("%q", flags.Arg(2))
6495
} else {
65-
fmt.Printf("%s\n", data)
96+
fmt.Println(voice.PreviewUrl)
97+
return nil
6698
}
67-
return nil
6899
}
69100
}
70101

71-
func ElevenlabsTextToSpeech(client *elevenlabs.Client, flags *Flags) CommandFn {
102+
func elevenlabsTextToSpeech(client *elevenlabs.Client, flags *Flags) CommandFn {
72103
return func() error {
73104
// Determine the voice to use
74105
voice, err := flags.GetString("elevenlabs-voice")
@@ -77,6 +108,10 @@ func ElevenlabsTextToSpeech(client *elevenlabs.Client, flags *Flags) CommandFn {
77108
} else if voice == "" {
78109
return fmt.Errorf("missing argument: -elevenlabs-voice")
79110
}
111+
voice, err = elevenlabsGetVoiceId(client, voice)
112+
if err != nil {
113+
return err
114+
}
80115

81116
data, err := client.TextToSpeech(flags.Arg(2), voice)
82117
if err != nil {
@@ -87,3 +122,22 @@ func ElevenlabsTextToSpeech(client *elevenlabs.Client, flags *Flags) CommandFn {
87122
return nil
88123
}
89124
}
125+
126+
/////////////////////////////////////////////////////////////////////
127+
// PRIVATE METHODS
128+
129+
// return a voice-id given a parameter, which can be a voice-id or name
130+
func elevenlabsGetVoiceId(client *elevenlabs.Client, voice string) (string, error) {
131+
if reVoiceId.MatchString(voice) {
132+
return voice, nil
133+
} else if voices, err := client.Voices(); err != nil {
134+
return "", err
135+
} else {
136+
for _, v := range voices {
137+
if strings.EqualFold(v.Name, voice) || v.Id == voice {
138+
return v.Id, nil
139+
}
140+
}
141+
}
142+
return "", ErrNotFound.Withf("%q", voice)
143+
}

cmd/cli/flags.go

+60-9
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ package main
22

33
import (
44
"flag"
5+
"fmt"
56
"os"
7+
"path/filepath"
8+
"strconv"
69
"time"
710

811
// Packages
@@ -15,8 +18,7 @@ import (
1518

1619
type Flags struct {
1720
*flag.FlagSet
18-
19-
writer *writer.Writer
21+
writer *writer.TableWriter
2022
}
2123

2224
type FlagsRegister func(*Flags)
@@ -31,6 +33,8 @@ func NewFlags(name string, args []string, register ...FlagsRegister) (*Flags, er
3133
// Register flags
3234
flags.Bool("debug", false, "Enable debug logging")
3335
flags.Duration("timeout", 0, "Timeout")
36+
flags.String("out", "txt", "Output format <txt|csv|tsv|json> or file name <filename>.<txt|csv|tsv|json>")
37+
flags.String("cols", "", "Comma-separated list of columns to output")
3438
for _, fn := range register {
3539
fn(flags)
3640
}
@@ -41,11 +45,7 @@ func NewFlags(name string, args []string, register ...FlagsRegister) (*Flags, er
4145
}
4246

4347
// Create a writer
44-
if w, err := writer.New(os.Stdout); err != nil {
45-
return nil, err
46-
} else {
47-
flags.writer = w
48-
}
48+
flags.writer = writer.New(os.Stdout)
4949

5050
// Return success
5151
return flags, nil
@@ -62,6 +62,33 @@ func (flags *Flags) Timeout() time.Duration {
6262
return flags.Lookup("timeout").Value.(flag.Getter).Get().(time.Duration)
6363
}
6464

65+
func (flags *Flags) GetOut() string {
66+
v, _ := flags.GetString("out")
67+
return v
68+
}
69+
70+
// Return a filename for output, returns an empty string if the output
71+
// argument is not a filename (it requires an extension)
72+
func (flags *Flags) GetOutFilename(def string, n uint) string {
73+
filename := flags.GetOut()
74+
if filename == "" {
75+
filename = filepath.Base(def)
76+
}
77+
if filename == "" {
78+
return ""
79+
}
80+
ext := filepath.Ext(filename)
81+
if ext == "" {
82+
return ""
83+
}
84+
if n > 0 {
85+
filename = filename[:len(filename)-len(ext)] + "-" + fmt.Sprint(n) + ext
86+
} else {
87+
filename = filename[:len(filename)-len(ext)] + ext
88+
}
89+
return filepath.Clean(filename)
90+
}
91+
6592
func (flags *Flags) GetString(key string) (string, error) {
6693
if flag := flags.Lookup(key); flag == nil {
6794
return "", errors.ErrNotFound.With(key)
@@ -70,6 +97,30 @@ func (flags *Flags) GetString(key string) (string, error) {
7097
}
7198
}
7299

73-
func (flags *Flags) Write(v writer.TableWriter) error {
74-
return flags.writer.Write(v)
100+
func (flags *Flags) GetUint(key string) (uint, error) {
101+
if flag := flags.Lookup(key); flag == nil {
102+
return 0, errors.ErrNotFound.With(key)
103+
} else if v, err := strconv.ParseUint(os.ExpandEnv(flag.Value.String()), 10, 64); err != nil {
104+
return 0, errors.ErrBadParameter.With(key)
105+
} else {
106+
return uint(v), nil
107+
}
108+
}
109+
110+
func (flags *Flags) Write(v any) error {
111+
opts := []writer.TableOpt{}
112+
113+
// Set terminal options
114+
opts = append(opts, TerminalOpts(flags.Output())...)
115+
116+
// Set output options
117+
switch flags.GetOut() {
118+
case "text", "txt", "ascii":
119+
opts = append(opts, writer.OptText('|', true, 0))
120+
case "csv":
121+
opts = append(opts, writer.OptCSV(',', true))
122+
case "tsv":
123+
opts = append(opts, writer.OptCSV('\t', true))
124+
}
125+
return flags.writer.Write(v, opts...)
75126
}

cmd/cli/main.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ import (
77
"path"
88

99
"github.com/mutablelogic/go-client/pkg/client"
10+
"github.com/pkg/errors"
1011
)
1112

1213
func main() {
1314
name := path.Base(os.Args[0])
14-
flags, err := NewFlags(name, os.Args[1:], ElevenlabsFlags, OpenAIFlags)
15+
flags, err := NewFlags(name, os.Args[1:], OpenAIFlags)
1516
if err != nil {
1617
if err != flag.ErrHelp {
1718
fmt.Fprintln(os.Stderr, err)
@@ -37,11 +38,11 @@ func main() {
3738
fmt.Fprintln(os.Stderr, err)
3839
os.Exit(1)
3940
}
40-
cmd, err = ElevenlabsRegister(cmd, opts, flags)
41-
if err != nil {
42-
fmt.Fprintln(os.Stderr, err)
43-
os.Exit(1)
44-
}
41+
/* cmd, err = ElevenlabsRegister(cmd, opts, flags)
42+
if err != nil {
43+
fmt.Fprintln(os.Stderr, err)
44+
os.Exit(1)
45+
}*/
4546
cmd, err = OpenAIRegister(cmd, opts, flags)
4647
if err != nil {
4748
fmt.Fprintln(os.Stderr, err)
@@ -50,7 +51,11 @@ func main() {
5051

5152
// Run command
5253
if err := Run(cmd, flags); err != nil {
53-
fmt.Fprintln(os.Stderr, err)
54+
if errors.Is(err, flag.ErrHelp) {
55+
PrintCommands(flags, cmd)
56+
} else {
57+
fmt.Fprintln(os.Stderr, err)
58+
}
5459
os.Exit(1)
5560
}
5661
}

0 commit comments

Comments
 (0)