Skip to content

Commit a51ee95

Browse files
committed
Added text box SDK
1 parent bd425f1 commit a51ee95

File tree

2 files changed

+253
-0
lines changed

2 files changed

+253
-0
lines changed

textbox/textbox.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package textbox
2+
3+
import (
4+
"encoding/json"
5+
"io"
6+
"io/ioutil"
7+
"net/http"
8+
"net/url"
9+
"strings"
10+
"time"
11+
12+
"github.com/machinebox/sdk-go/x/boxutil"
13+
"github.com/pkg/errors"
14+
)
15+
16+
// Analysis represents an analysis of text.
17+
type Analysis struct {
18+
Sentences []Sentence `json:"sentences"`
19+
Keywords []Keyword `json:"keywords"`
20+
}
21+
22+
// Sentence represents a single sentence of text.
23+
type Sentence struct {
24+
// Text is the text of the sentence.
25+
Text string `json:"text"`
26+
// Start is the absolute start position of the sentence (in the original text).
27+
Start int `json:"start"`
28+
// Start is the absolute end position of the sentence (in the original text).
29+
End int `json:"end"`
30+
// Sentiment is a probability score (between 0 and 1) of the sentiment of the sentence;
31+
// higher is more positive, lower is more negative.
32+
Sentiment float64 `json:"sentiment"`
33+
// Entities represents entities discovered in the text.
34+
Entities []Entity `json:"entities"`
35+
}
36+
37+
// Entity represents an entity discovered in the text.
38+
type Entity struct {
39+
// Type is a string describing the kind of entity.
40+
Type string `json:"type"`
41+
// Text is the text of the entity.
42+
Text string `json:"text"`
43+
// Start is the absolute start position of the entity (in the original text).
44+
Start int `json:"start"`
45+
// Start is the absolute end position of the entity (in the original text).
46+
End int `json:"end"`
47+
}
48+
49+
// Keyword represents a key word.
50+
type Keyword struct {
51+
Keyword string `json:"keyword"`
52+
}
53+
54+
// Client is an HTTP client that can make requests to the box.
55+
type Client struct {
56+
addr string
57+
58+
// HTTPClient is the http.Client that will be used to
59+
// make requests.
60+
HTTPClient *http.Client
61+
}
62+
63+
// New makes a new Client.
64+
func New(addr string) *Client {
65+
return &Client{
66+
addr: addr,
67+
HTTPClient: &http.Client{
68+
Timeout: 10 * time.Second,
69+
},
70+
}
71+
}
72+
73+
// Info gets the details about the box.
74+
func (c *Client) Info() (*boxutil.Info, error) {
75+
var info boxutil.Info
76+
u, err := url.Parse(c.addr + "/info")
77+
if err != nil {
78+
return nil, err
79+
}
80+
if !u.IsAbs() {
81+
return nil, errors.New("box address must be absolute")
82+
}
83+
resp, err := c.HTTPClient.Get(u.String())
84+
if err != nil {
85+
return nil, err
86+
}
87+
defer resp.Body.Close()
88+
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
89+
return nil, err
90+
}
91+
return &info, nil
92+
}
93+
94+
// Check passes the text from the Reader to Textbox for analysis.
95+
func (c *Client) Check(r io.Reader) (*Analysis, error) {
96+
u, err := url.Parse(c.addr + "/textbox/check")
97+
if err != nil {
98+
return nil, err
99+
}
100+
if !u.IsAbs() {
101+
return nil, errors.New("box address must be absolute")
102+
}
103+
vals := url.Values{}
104+
b, err := ioutil.ReadAll(r)
105+
if err != nil {
106+
return nil, err
107+
}
108+
vals.Set("text", string(b))
109+
req, err := http.NewRequest("POST", u.String(), strings.NewReader(vals.Encode()))
110+
if err != nil {
111+
return nil, err
112+
}
113+
resp, err := c.HTTPClient.Do(req)
114+
if err != nil {
115+
return nil, err
116+
}
117+
defer resp.Body.Close()
118+
var response struct {
119+
Success bool
120+
Error string
121+
Sentences []Sentence `json:"sentences"`
122+
Keywords []Keyword `json:"keywords"`
123+
}
124+
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
125+
return nil, errors.Wrap(err, "decoding response")
126+
}
127+
if !response.Success {
128+
return nil, ErrTextbox(response.Error)
129+
}
130+
return &Analysis{
131+
Sentences: response.Sentences,
132+
Keywords: response.Keywords,
133+
}, nil
134+
}
135+
136+
// ErrTextbox represents an error from nudebox.
137+
type ErrTextbox string
138+
139+
func (e ErrTextbox) Error() string {
140+
return "textbox: " + string(e)
141+
}

textbox/textbox_test.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package textbox_test
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"net/http/httptest"
7+
"strings"
8+
"testing"
9+
10+
"github.com/machinebox/sdk-go/textbox"
11+
"github.com/matryer/is"
12+
)
13+
14+
func TestInfo(t *testing.T) {
15+
is := is.New(t)
16+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
17+
is.Equal(r.Method, "GET")
18+
is.Equal(r.URL.Path, "/info")
19+
io.WriteString(w, `{
20+
"name": "textbox",
21+
"version": 1,
22+
"build": "abcdefg",
23+
"status": "ready"
24+
}`)
25+
}))
26+
defer srv.Close()
27+
nb := textbox.New(srv.URL)
28+
info, err := nb.Info()
29+
is.NoErr(err)
30+
is.Equal(info.Name, "textbox")
31+
is.Equal(info.Version, 1)
32+
is.Equal(info.Build, "abcdefg")
33+
is.Equal(info.Status, "ready")
34+
}
35+
36+
func TestCheck(t *testing.T) {
37+
result := `{
38+
"success": true,
39+
"sentences": [
40+
{
41+
"text": "I really love Machina, who is the MachineBox mascot designed by Ashley McNamara.",
42+
"start": 0,
43+
"end": 80,
44+
"sentiment": 0.7128883004188538,
45+
"entities": [
46+
{
47+
"text": "Machina",
48+
"start": 14,
49+
"end": 21,
50+
"type": "person"
51+
},
52+
{
53+
"text": "MachineBox",
54+
"start": 34,
55+
"end": 44,
56+
"type": "organization"
57+
},
58+
{
59+
"text": "Ashley McNamara",
60+
"start": 64,
61+
"end": 79,
62+
"type": "person"
63+
}
64+
]
65+
}
66+
],
67+
"keywords": [
68+
{
69+
"keyword": "ashley mcnamara"
70+
},
71+
{
72+
"keyword": "machinebox mascot"
73+
},
74+
{
75+
"keyword": "machina"
76+
}
77+
]
78+
}`
79+
is := is.New(t)
80+
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81+
is.Equal(r.Method, "POST")
82+
is.Equal(r.URL.Path, "/textbox/check")
83+
io.WriteString(w, result)
84+
}))
85+
defer srv.Close()
86+
tb := textbox.New(srv.URL)
87+
src := strings.NewReader(`I really love Machina, who is the MachineBox mascot designed by Ashley McNamara.`)
88+
res, err := tb.Check(src)
89+
is.NoErr(err)
90+
is.Equal(len(res.Keywords), 3)
91+
is.Equal(res.Keywords[0].Keyword, "ashley mcnamara")
92+
is.Equal(res.Keywords[1].Keyword, "machinebox mascot")
93+
is.Equal(res.Keywords[2].Keyword, "machina")
94+
is.Equal(len(res.Sentences), 1)
95+
is.Equal(res.Sentences[0].Text, "I really love Machina, who is the MachineBox mascot designed by Ashley McNamara.")
96+
is.Equal(res.Sentences[0].Start, 0)
97+
is.Equal(res.Sentences[0].End, 80)
98+
is.Equal(res.Sentences[0].Sentiment, 0.7128883004188538)
99+
is.Equal(len(res.Sentences[0].Entities), 3)
100+
is.Equal(res.Sentences[0].Entities[0].Text, "Machina")
101+
is.Equal(res.Sentences[0].Entities[0].Type, "person")
102+
is.Equal(res.Sentences[0].Entities[0].Start, 14)
103+
is.Equal(res.Sentences[0].Entities[0].End, 21)
104+
is.Equal(res.Sentences[0].Entities[1].Text, "MachineBox")
105+
is.Equal(res.Sentences[0].Entities[1].Type, "organization")
106+
is.Equal(res.Sentences[0].Entities[1].Start, 34)
107+
is.Equal(res.Sentences[0].Entities[1].End, 44)
108+
is.Equal(res.Sentences[0].Entities[2].Text, "Ashley McNamara")
109+
is.Equal(res.Sentences[0].Entities[2].Type, "person")
110+
is.Equal(res.Sentences[0].Entities[2].Start, 64)
111+
is.Equal(res.Sentences[0].Entities[2].End, 79)
112+
}

0 commit comments

Comments
 (0)