Skip to content

Commit

Permalink
Merge pull request #5 from euiko/add-command-execution-context
Browse files Browse the repository at this point in the history
add: context that are bounds to command execution
  • Loading branch information
galihrivanto authored Jun 18, 2021
2 parents be3c65d + d03b875 commit 4b08ee6
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 22 deletions.
10 changes: 10 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ type Options struct {

// set verbosity
verbose bool

// allow to suppress warning
suppressWarning bool
}

// CommandName customize inkscape executable name
Expand All @@ -43,6 +46,13 @@ func CommandQueueLength(length int) Option {
}
}

// SuppressWarning override default suppress warning option, that are enabled
func SuppressWarning(suppress bool) Option {
return func(o *Options) {
o.suppressWarning = suppress
}
}

// Verbose override log verbosity
// useful for debugging
func Verbose(verbose bool) Option {
Expand Down
61 changes: 42 additions & 19 deletions proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
var (
ErrCommandNotAvailable = errors.New("inkscape not available")
ErrCommandNotReady = errors.New("inkscape not ready")
ErrCommandExecCanceled = errors.New("command execution canceled")
)

// bytes.Buffer pool
Expand All @@ -36,7 +37,7 @@ type chanWriter struct {
func (w *chanWriter) Write(data []byte) (int, error) {

// look like the buffer being reused internally by the exec.Command
// so we can directly read the buffer in another goroutine while still being used in exec.Command goroutine
// so we can't directly read the buffer in another goroutine while still being used in exec.Command goroutine

// copy to be written buffer and pass it into channel
bufferToWrite := make([]byte, len(data))
Expand All @@ -46,7 +47,7 @@ func (w *chanWriter) Write(data []byte) (int, error) {
return written, nil
}

// Proxy runs inkspace instance in background and
// Proxy runs inkscape instance in background and
// provides mechanism to interfacing with running
// instance via stdin
type Proxy struct {
Expand Down Expand Up @@ -144,17 +145,22 @@ wait:
}

case bytesErr := <-stderrC:
log.Println("stderr :", bytesErr)
if len(bytesErr) == 0 {
break
}

if bytes.Contains(bytesErr, []byte("WARNING")) {
break
// only skip warning when option suppressWarning are true
if p.options.suppressWarning {
if bytes.Contains(bytesErr, []byte("WARNING")) {
break
}
}

p.stderr <- bytes.TrimSpace(bytesErr)

case bytesOut := <-stdoutC:
log.Println("stderr :", bytesOut)
if len(bytesOut) == 0 {
break
}
Expand Down Expand Up @@ -195,7 +201,7 @@ func (p *Proxy) Run(args ...string) error {
// Close satisfy io.Closer interface
func (p *Proxy) Close() error {
// send quit command
_, err := p.sendCommand([]byte(quitCommand), false)
_, err := p.sendCommand(context.Background(), []byte(quitCommand), false)

p.cancel()
close(p.requestLimiter)
Expand All @@ -206,7 +212,7 @@ func (p *Proxy) Close() error {
return err
}

func (p *Proxy) sendCommand(b []byte, waitPrompt ...bool) ([]byte, error) {
func (p *Proxy) sendCommand(ctx context.Context, b []byte, waitPrompt ...bool) ([]byte, error) {
wait := true
if len(waitPrompt) > 0 {
wait = waitPrompt[0]
Expand Down Expand Up @@ -246,17 +252,22 @@ func (p *Proxy) sendCommand(b []byte, waitPrompt ...bool) ([]byte, error) {

waitLoop:
for {
select {
// wait till context canceled, early return
case <-ctx.Done():
return output, ErrCommandExecCanceled
// wait until received prompt
bytesOut := <-p.stdout
p.debug(string(bytesOut))
parts := bytes.Split(bytesOut, []byte("\n"))
for _, part := range parts {
if isPrompt(part) {
break waitLoop
case bytesOut := <-p.stdout:
p.debug(string(bytesOut))
parts := bytes.Split(bytesOut, []byte("\n"))
for _, part := range parts {
if isPrompt(part) {
break waitLoop
}
}
}

output = append(output, bytesOut...)
output = append(output, bytesOut...)
}
}

// drain error channel
Expand All @@ -278,20 +289,31 @@ errLoop:

// RawCommands send inkscape shell commands
func (p *Proxy) RawCommands(args ...string) ([]byte, error) {
return p.RawCommandsContext(context.Background(), args...)
}

// RawCommandsContext send inkscape shell commands that are bounded into specific context
func (p *Proxy) RawCommandsContext(ctx context.Context, args ...string) ([]byte, error) {
buffer := bufferPool.Get()
defer bufferPool.Put(buffer)

// construct command buffer
buffer.WriteString(strings.Join(args, ";"))

res, err := p.sendCommand(buffer.Bytes())
res, err := p.sendCommand(ctx, buffer.Bytes())

return res, err
}

// Svg2Pdf convert svg input file to output pdf file
func (p *Proxy) Svg2Pdf(svgIn, pdfOut string) error {
res, err := p.RawCommands(
return p.Svg2PdfContext(context.Background(), svgIn, pdfOut)
}

// Svg2PdfContext convert svg input file to output pdf file that are bounded into specific context
func (p *Proxy) Svg2PdfContext(ctx context.Context, svgIn, pdfOut string) error {
res, err := p.RawCommandsContext(
ctx,
FileOpen(svgIn),
ExportFileName(pdfOut),
ExportDo(),
Expand All @@ -310,9 +332,10 @@ func (p *Proxy) Svg2Pdf(svgIn, pdfOut string) error {
func NewProxy(opts ...Option) *Proxy {
// default value
options := Options{
commandName: defaultCmdName,
maxRetry: 5,
verbose: false,
commandName: defaultCmdName,
maxRetry: 5,
verbose: false,
suppressWarning: true,
}

// merge options
Expand Down
43 changes: 43 additions & 0 deletions proxy_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package inkscape

import (
"context"
"fmt"
"math/rand"
"os"
"testing"
"time"
)

// TestConcurrent tests the library usage in concurrent environment
func TestConcurrent(t *testing.T) {
tempFiles := make([]string, 0)
defer func() {
Expand Down Expand Up @@ -34,6 +38,45 @@ func TestConcurrent(t *testing.T) {
}
}

// TestExecContext tests against command execution within context boundary
func TestExecContext(t *testing.T) {
const file = "circle.svg"
n := rand.Intn(1000)
tmpFile := fmt.Sprintf("%s.tmp.%d.pdf", file, n)
defer func() {
os.Remove(tmpFile)
}()

proxy := NewProxy(Verbose(true))
err := proxy.Run()
if err != nil {
t.Error(err)
t.FailNow()
}
defer proxy.Close()

// gives very short life of execution context to tests
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*1)
done := make(chan struct{})
defer cancel()

go func() {
// this command expected to run no more than specified timeout duration
err := proxy.Svg2PdfContext(ctx, file, tmpFile)
if err != nil {
if err != ErrCommandExecCanceled {
t.Error(err)
}
}
if err == nil {
t.Error("expected command to be canceled, got success command execution")
}
done <- struct{}{}
}()

<-done
}

func TestOpenFail(t *testing.T) {
proxy := NewProxy(Verbose(true))
err := proxy.Run()
Expand Down
14 changes: 11 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,18 @@ go get github.com/galihrivanto/go-inkscape
```

# **simple usage**

```go
func main() {
var (
package main

import (
"flag"
"fmt"
"github.com/galihrivanto/go-inkscape"
"os"
)

var (
svgInput string
pdfOutput string
)
Expand Down Expand Up @@ -49,7 +58,6 @@ func main() {

fmt.Println("done!!")
}
}
```

# **advanced usage**
Expand Down

0 comments on commit 4b08ee6

Please sign in to comment.