Skip to content

Commit 3d4a331

Browse files
committed
[output] add table format support
1 parent becb9b0 commit 3d4a331

5 files changed

Lines changed: 163 additions & 7 deletions

File tree

‎README.md‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,12 @@ The CLI can be configured using environment variables or command-line flags. You
116116

117117
### Output Formats
118118

119-
The CLI supports three output formats:
119+
The CLI supports four output formats:
120120

121121
1. `text`: Human-readable text output (default)
122122
2. `json`: Pretty printed JSON-formatted output
123123
3. `raw`: One-line JSON-formatted output
124+
4. `table`: Tab-aligned columnar output for lists and sub-tables
124125

125126
Please note that when the exit code is not `0`, the error description is printed to stderr without any formatting.
126127

@@ -407,6 +408,14 @@ Recipients:
407408
{"id":"zXDYfTmTVf3iMd16zzdBj","state":"Pending","isHashed":false,"isEncrypted":false,"recipients":[{"phoneNumber":"+12025550123","state":"Pending"},{"phoneNumber":"+12025550124","state":"Pending"}],"states":{}}
408409
```
409410

411+
**Table**
412+
413+
```text
414+
ID EVENT URL DEVICE ID
415+
123e4567-e89b-12d3-a456-426614174000 sms:received https://example.com/webhook dev-abc
416+
def45678-e89b-12d3-a456-426614174000 sms:sent https://example.com/other
417+
```
418+
410419
<p align="right">(<a href="#readme-top">back to top</a>)</p>
411420

412421
## 👥 Contributing

‎cmd/smsgate/smsgate.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func main() {
7777
&cli.StringFlag{
7878
Name: "format",
7979
Category: categoryOutput,
80-
Usage: "Output format. Supported: text, json, raw",
80+
Usage: "Output format. Supported: text, json, raw, table",
8181
Required: false,
8282
Value: string(output.Text),
8383
Aliases: []string{"f"},

‎internal/core/output/output.go‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import (
99
type Format string
1010

1111
const (
12-
Text Format = "text"
13-
JSON Format = "json"
14-
RAW Format = "raw"
12+
Text Format = "text"
13+
JSON Format = "json"
14+
RAW Format = "raw"
15+
Table Format = "table"
1516
)
1617

1718
type Renderer interface {
@@ -22,6 +23,8 @@ type Renderer interface {
2223
Success() (string, error)
2324
}
2425

26+
const EmptyResult = "Empty result"
27+
2528
var ErrUnsupportedFormat = errors.New("unsupported format")
2629

2730
func New(format Format) (Renderer, error) {
@@ -32,6 +35,8 @@ func New(format Format) (Renderer, error) {
3235
return NewJSONOutput(), nil
3336
case RAW:
3437
return NewRawOutput(), nil
38+
case Table:
39+
return NewTableOutput(), nil
3540
default:
3641
return nil, ErrUnsupportedFormat
3742
}

‎internal/core/output/table.go‎

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package output
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"text/tabwriter"
7+
"time"
8+
9+
"github.com/android-sms-gateway/client-go/smsgateway"
10+
)
11+
12+
const tabwriterPadding = 2
13+
14+
type TableOutput struct{}
15+
16+
func NewTableOutput() *TableOutput {
17+
return &TableOutput{}
18+
}
19+
20+
func (*TableOutput) MessageState(src smsgateway.MessageState) (string, error) {
21+
var b strings.Builder
22+
23+
fmt.Fprintf(&b, "ID:\t%s\n", src.ID)
24+
fmt.Fprintf(&b, "Device ID:\t%s\n", src.DeviceID)
25+
fmt.Fprintf(&b, "State:\t%s\n", src.State)
26+
fmt.Fprintf(&b, "IsHashed:\t%s\n", boolToString(src.IsHashed))
27+
fmt.Fprintf(&b, "IsEncrypted:\t%s\n", boolToString(src.IsEncrypted))
28+
29+
if len(src.Recipients) > 0 {
30+
b.WriteString("\nRecipients:\n")
31+
tw := tabwriter.NewWriter(&b, 0, 0, tabwriterPadding, ' ', 0)
32+
fmt.Fprintln(tw, "PHONE\tSTATE\tERROR")
33+
for _, r := range src.Recipients {
34+
errStr := ""
35+
if r.Error != nil {
36+
errStr = *r.Error
37+
}
38+
fmt.Fprintf(tw, "%s\t%s\t%s\n", r.PhoneNumber, r.State, errStr)
39+
}
40+
if err := tw.Flush(); err != nil {
41+
return "", fmt.Errorf("flush tabwriter: %w", err)
42+
}
43+
}
44+
45+
if len(src.States) > 0 {
46+
messageStates := []string{
47+
string(smsgateway.ProcessingStatePending),
48+
string(smsgateway.ProcessingStateProcessed),
49+
string(smsgateway.ProcessingStateSent),
50+
string(smsgateway.ProcessingStateDelivered),
51+
string(smsgateway.ProcessingStateFailed),
52+
}
53+
54+
b.WriteString("\nStates:\n")
55+
tw := tabwriter.NewWriter(&b, 0, 0, tabwriterPadding, ' ', 0)
56+
fmt.Fprintln(tw, "STATE\tTIME")
57+
for _, k := range messageStates {
58+
v, ok := src.States[k]
59+
if !ok {
60+
continue
61+
}
62+
fmt.Fprintf(tw, "%s\t%s\n", k, v.Local().Format(time.RFC3339))
63+
}
64+
if err := tw.Flush(); err != nil {
65+
return "", fmt.Errorf("flush tabwriter: %w", err)
66+
}
67+
}
68+
69+
return strings.TrimRight(b.String(), "\n"), nil
70+
}
71+
72+
func (*TableOutput) Logs(src []smsgateway.LogEntry) (string, error) {
73+
if len(src) == 0 {
74+
return EmptyResult, nil
75+
}
76+
77+
var b strings.Builder
78+
tw := tabwriter.NewWriter(&b, 0, 0, tabwriterPadding, ' ', 0)
79+
80+
fmt.Fprintln(tw, "ID\tPRIORITY\tMODULE\tMESSAGE\tCREATED AT")
81+
for _, entry := range src {
82+
fmt.Fprintf(
83+
tw,
84+
"%d\t%s\t%s\t%s\t%s\n",
85+
entry.ID,
86+
entry.Priority,
87+
entry.Module,
88+
entry.Message,
89+
entry.CreatedAt.Local().Format(time.RFC3339),
90+
)
91+
}
92+
93+
if err := tw.Flush(); err != nil {
94+
return "", fmt.Errorf("flush tabwriter: %w", err)
95+
}
96+
97+
return strings.TrimRight(b.String(), "\n"), nil
98+
}
99+
100+
func (*TableOutput) Webhook(src smsgateway.Webhook) (string, error) {
101+
var b strings.Builder
102+
103+
fmt.Fprintf(&b, "ID:\t%s\n", src.ID)
104+
fmt.Fprintf(&b, "Event:\t%s\n", src.Event)
105+
fmt.Fprintf(&b, "URL:\t%s\n", src.URL)
106+
107+
if src.DeviceID != nil {
108+
fmt.Fprintf(&b, "Device ID:\t%s\n", *src.DeviceID)
109+
}
110+
111+
return b.String(), nil
112+
}
113+
114+
func (o *TableOutput) Webhooks(src []smsgateway.Webhook) (string, error) {
115+
if len(src) == 0 {
116+
return EmptyResult, nil
117+
}
118+
119+
var b strings.Builder
120+
tw := tabwriter.NewWriter(&b, 0, 0, tabwriterPadding, ' ', 0)
121+
122+
fmt.Fprintln(tw, "ID\tEVENT\tURL\tDEVICE ID")
123+
for _, w := range src {
124+
deviceID := ""
125+
if w.DeviceID != nil {
126+
deviceID = *w.DeviceID
127+
}
128+
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\n", w.ID, w.Event, w.URL, deviceID)
129+
}
130+
131+
if err := tw.Flush(); err != nil {
132+
return "", fmt.Errorf("flush tabwriter: %w", err)
133+
}
134+
135+
return strings.TrimRight(b.String(), "\n"), nil
136+
}
137+
138+
func (*TableOutput) Success() (string, error) {
139+
return "Success", nil
140+
}
141+
142+
var _ Renderer = (*TableOutput)(nil)

‎internal/core/output/text.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (*TextOutput) MessageState(src smsgateway.MessageState) (string, error) {
7373

7474
func (*TextOutput) Logs(src []smsgateway.LogEntry) (string, error) {
7575
if len(src) == 0 {
76-
return "Empty result", nil
76+
return EmptyResult, nil
7777
}
7878

7979
builder := strings.Builder{}
@@ -126,7 +126,7 @@ func (*TextOutput) Webhook(src smsgateway.Webhook) (string, error) {
126126
// Returns the formatted string and any error encountered during formatting.
127127
func (o *TextOutput) Webhooks(src []smsgateway.Webhook) (string, error) {
128128
if len(src) == 0 {
129-
return "Empty result", nil
129+
return EmptyResult, nil
130130
}
131131

132132
builder := strings.Builder{}

0 commit comments

Comments
 (0)