Skip to content

Commit

Permalink
Merge pull request #48 from vladimirvivien/docs-examples
Browse files Browse the repository at this point in the history
Add missing top-level functions; new examples added
  • Loading branch information
vladimirvivien authored Nov 6, 2022
2 parents 07c6a3d + 6f5e046 commit 35a3a02
Show file tree
Hide file tree
Showing 13 changed files with 223 additions and 71 deletions.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,38 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/vladimirvivien/echo)](https://goreportcard.com/report/github.com/vladimirvivien/echo)
![Build](https://github.com/vladimirvivien/gexe/actions/workflows/build.yml/badge.svg)
# Project `gexe`
Script-like OS interaction wrapped in the security and type safety of the Go programming language!
Package with script-like API for system operation and automation!

The goal of project `gexe` is to make it dead simple to write code that interacts with the OS (and/or other components) with the type safety of the Go programming language.
The goal of project `gexe` is to make it simple to write code for system operation and task automation using a script-like API that offers the security and the type safety of the Go programming language (see [/examples](/examples/)).

> NOTE: this project got renamed from Echo to Gexe (see Project Name Change)
## What can you do with `gexe`?
* Parse and execute OS commands provided as plain and clear text as you would in a shell.
* Parse and execute OS plain text commands, as you would in a shell.
* Support for variable expansion in command string (i.e. `gexe.Run("echo $HOME")`)
* Ability to pipe processes: `gexe.Pipe("cat /etc/hosts", "wc -l")`
* Run processes concurrently: `gexe.RunConcur('wget https://example.com/files'; "date")`
* Get process information (i.e. PID, status, exit code, etc)
* Stream data from stdout while process is executing
* Get program information (i.e. args, binary name, working dir, etc)
* Easily read file content into different targets (string, bytes, io.Writer, etc)
* Easily write file content from different sources (i.e. string, bytes, io.Reader, etc)
* Easily read and write file content using different sources (string, bytes, io.Writer, etc)
* Integrate with your shell script using `go run`

## Using `gexe`

### Get the package
```bash=
```bash
go get github.com/vladimirvivien/gexe
```

### Run a process
The following executes command `echo "Hello World!"` and prints the result:
```go=
```go
fmt.Println(gexe.Run(`echo "Hello World!"`))
```

Alternatively, you can create your own `gexe` session for more control and error hanlding:

```go=
```go
g := gexe.New()
proc := g.RunProc(`echo "Hello World"`)
if proc.Err() != nil {
Expand Down
11 changes: 8 additions & 3 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# `gexe ` Examples
* [build](./build/main.go) - Show how to build Go binaries for multiple platforms and OSes.

* [build](./build/main.go) - Shows how to build Go binaries for multiple platforms and OSes.
* [concurrent](./concurrent/main.go) - Shows how to execute OS commands concurrently
* [filesys](./filesys/main.go) - Shows how to use the `fs` package to easily write content
* [git](./git/main.go) - Uses git to print logs and commit info
* [helloecho](./helloecho/main.go) - Simple example
* [ping](./ping/main.go) - Stream output of long-running processes
* [hellogexe](./hellogexe/main.go) - Simple example to show how to get started
* [http](./http/main.go) - Shows the use of the gexe `http` package to retrieve HTTP remote resources.
* [ping](./ping/main.go) - Example of streaming output of long-running processes
* [pipe](./pipe/main.go) - An examples that shows how to pipe multiple OS commands
36 changes: 36 additions & 0 deletions examples/concurrent/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"fmt"
"log"
"os"

"github.com/vladimirvivien/gexe"
)

// This example shows how to execute OS commands concurrently and wait for the result.
func main() {

fmt.Println("Downloading 3 books concurrently from Gutenberg")

// The following launches each command concurrently to download long text from Gutenberg.
// The download should be faster than if ran sequentially.
result := gexe.RunConcur(
"wget -O /tmp/thenegro.txt https://www.gutenberg.org/cache/epub/15359/pg15359.txt",
"wget -O /tmp/fleece.txt https://www.gutenberg.org/cache/epub/15265/pg15265.txt",
"wget -O /tmp/conversation.txt https://www.gutenberg.org/cache/epub/31254/pg31254.txt",
)

// inspect result or check for errors.
if len(result.ErrProcs()) > 0 {
log.Println("One or more commands failed")
}

for _, path := range []string{"/tmp/thenegro.txt", "/tmp/fleece.txt", "/tmp/conversation.txt"} {
if _, err := os.Stat(path); err == nil {
fmt.Printf("file %s downloaded OK\n", path)
os.RemoveAll(path)
}
}

}
23 changes: 7 additions & 16 deletions examples/filesys/main.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,26 @@
package main

import (
"bytes"
"fmt"
"os"

"github.com/vladimirvivien/gexe"
"github.com/vladimirvivien/gexe/str"
)

// This example uses local git to create a file with commit logs.
// This example shows how to use the fs package to create/write
// files with simplicity.
func main() {
buf := new(bytes.Buffer)
cmd := `/bin/sh -c "git log --reverse --abbrev-commit --pretty=oneline | cut -d ' ' -f1"`
for _, p := range str.SplitLines(gexe.Run(cmd)) {
gexe.SetVar("patch", p)
cmd := `/bin/sh -c "git show --abbrev-commit -s --pretty=format:'%h %s (%an) %n' ${patch}"`
buf.WriteString(fmt.Sprintf("%s\n", gexe.Run(cmd)))
}
fmt.Println("Download and saving text to /tmp/thenegro.txt")

gitfile := "./gitlog.txt"
// the following downloads a large W. E. Du Bois text from Gutenburg and writes locally.
cmd := "wget -O /tmp/thenegro.txt https://www.gutenberg.org/cache/epub/15359/pg15359.txt"

if w := gexe.Write(gitfile).From(buf); w.Err() != nil {
if w := gexe.Write("/tmp/thenegro.txt").From(gexe.RunProc(cmd).Out()); w.Err() != nil {
fmt.Println(w.Err())
os.Exit(1)
}

// read the file and print
fmt.Println(gexe.Read(gitfile).String())

if err := os.Remove(gitfile); err != nil {
if err := os.Remove("/tmp/thenegro.txt"); err != nil {
fmt.Println(err)
os.Exit(1)
}
Expand Down
File renamed without changes.
25 changes: 25 additions & 0 deletions examples/http/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"fmt"
"os"

"github.com/vladimirvivien/gexe"
)

func main() {
fmt.Println("Download and saving text")

// The following downloads a large text and saves the result
url := "https://www.gutenberg.org/cache/epub/2148/pg2148.txt"

if w := gexe.Write("/tmp/eapv2.txt").From(gexe.GetUrl(url).Body()); w.Err() != nil {
fmt.Println(w.Err())
os.Exit(1)
}

if err := os.Remove("/tmp/eapv2.txt"); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
24 changes: 24 additions & 0 deletions examples/pipe/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"fmt"
"log"

"github.com/vladimirvivien/gexe"
)

// This example shows how to use gexe to pipe commands.
// The first curl command will download a text by W. E. Du Bois, from Gutenberg,
// and pipe it to the second command which returns the number of lines in that text

func main() {
pipe := gexe.Pipe(
"curl https://www.gutenberg.org/cache/epub/15265/pg15265.txt",
"wc -l",
)

if len(pipe.ErrProcs()) > 0 {
log.Fatalf("failed to download file")
}
fmt.Printf("The Quest of the Silver Fleece, by W. E. B. Du Bois: has %s lines\n", pipe.LastProc().Result())
}
31 changes: 30 additions & 1 deletion functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gexe
import (
"github.com/vladimirvivien/gexe/exec"
"github.com/vladimirvivien/gexe/fs"
"github.com/vladimirvivien/gexe/http"
"github.com/vladimirvivien/gexe/prog"
"github.com/vladimirvivien/gexe/vars"
)
Expand Down Expand Up @@ -87,11 +88,29 @@ func Runout(cmdStr string) {
DefaultEcho.Runout(cmdStr)
}

// RunAll executes each command, in cmdStrs, successively.
// Commands returns a *exe.CommandBuilder to build a multi-command execution flow.
func Commands(cmdStrs ...string) *exec.CommandBuilder {
return DefaultEcho.Commands(cmdStrs...)
}

// StartAll starts the exection of each command sequentially and
// does not wait for their completion.
func StartAll(cmdStrs ...string) *exec.CommandResult {
return DefaultEcho.StartAll(cmdStrs...)
}

// RunAll executes each command, in cmdStrs, successively and wait for their
// completion.
func RunAll(cmdStrs ...string) *exec.CommandResult {
return DefaultEcho.RunAll(cmdStrs...)
}

// StartConcur starts the exection of each command concurrently and
// does not wait for their completion.
func StartConcur(cmdStrs ...string) *exec.CommandResult {
return DefaultEcho.StartConcur(cmdStrs...)
}

// RunConcur executes each command, in cmdStrs, concurrently and waits
// their completion.
func RunConcur(cmdStrs ...string) *exec.CommandResult {
Expand All @@ -116,6 +135,16 @@ func Write(path string) fs.FileWriter {
return DefaultEcho.Write(path)
}

// GetUrl creates a *http.ResourceReader to retrieve HTTP content
func GetUrl(url string) *http.ResourceReader {
return DefaultEcho.Get(url)
}

// PostUrl creates a *http.ResourceWriter to write content to an HTTP server
func PostUrl(url string) *http.ResourceWriter {
return DefaultEcho.Post(url)
}

// Prog returns program information via *prog.Info
func Prog() *prog.Info {
return DefaultEcho.Prog()
Expand Down
13 changes: 13 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gexe

import "github.com/vladimirvivien/gexe/http"

// Get creates a *http.ResourceReader to read resource content from HTTP server
func (e *Echo) Get(url string) *http.ResourceReader {
return http.Get(url)
}

// Post creates a *http.ResourceWriter to write content to an HTTP server
func (e *Echo) Post(url string) *http.ResourceWriter {
return http.Post(url)
}
37 changes: 20 additions & 17 deletions http/http_reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,68 +5,71 @@ import (
"net/http"
)

// HttpReader provides types and methods to read content from a server using HTTP
type HttpReader struct {
// ResourceReader provides types and methods to read content of resources from a server using HTTP
type ResourceReader struct {
client *http.Client
err error
url string
res *HttpResponse
res *Response
}

// Get initiates a "GET" operation for the specified resource
func Get(url string) *HttpReader {
return &HttpReader{url: url, client: &http.Client{}}
func Get(url string) *ResourceReader {
return &ResourceReader{url: url, client: &http.Client{}}
}

// Err returns the last known error
func (r *HttpReader) Err() error {
func (r *ResourceReader) Err() error {
return r.err
}

// Response returns the server's response info
func (r *HttpReader) Response() *HttpResponse {
func (r *ResourceReader) Response() *Response {
return r.res
}

// Bytes returns the server response as a []byte
func (b *HttpReader) Bytes() []byte {
if b.Do().Err() != nil {
func (b *ResourceReader) Bytes() []byte {
if err := b.Do().Err(); err != nil {
b.err = err
return nil
}
return b.read()
}

// String returns the server response as a string
func (b *HttpReader) String() string {
if b.Do().Err() != nil {
func (b *ResourceReader) String() string {
if err := b.Do().Err(); err != nil {
b.err = err
return ""
}
return string(b.read())
}

// Body returns an io.ReadCloser to stream the server response.
// NOTE: ensure to close the stream when finished.
func (r *HttpReader) Body() io.ReadCloser {
if r.Do().Err() != nil {
func (r *ResourceReader) Body() io.ReadCloser {
if err := r.Do().Err(); err != nil {
r.err = err
return nil
}
return r.res.body
}

// Do invokes the client.Get to "GET" the content from server
func (r *HttpReader) Do() *HttpReader {
func (r *ResourceReader) Do() *ResourceReader {
res, err := r.client.Get(r.url)
if err != nil {
r.err = err
r.res = &HttpResponse{}
r.res = &Response{}
return r
}
r.res = &HttpResponse{stat: res.Status, statCode: res.StatusCode, body: res.Body}
r.res = &Response{stat: res.Status, statCode: res.StatusCode, body: res.Body}
return r
}

// read reads the content of the response body and returns a []byte
func (r *HttpReader) read() []byte {
func (r *ResourceReader) read() []byte {
if r.res.body == nil {
return nil
}
Expand Down
10 changes: 5 additions & 5 deletions http/http_response.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@ package http

import "io"

// HttpResponse stores high level metadata and response body from the server
type HttpResponse struct {
// Response stores high level metadata and responses from HTTP request results
type Response struct {
stat string
statCode int
body io.ReadCloser
}

// Status returns the standard lib http.Response.Status value from the server
func (res *HttpResponse) Status() string {
func (res *Response) Status() string {
return res.stat
}

// StatusCode returns the standard lib http.Response.StatusCode value from the server
func (res *HttpResponse) StatusCode() int {
func (res *Response) StatusCode() int {
return res.statCode
}

// Body is io.ReadCloser stream to the content from serve.
// NOTE: ensure to call Close() if used directly.
func (res *HttpResponse) Body() io.ReadCloser {
func (res *Response) Body() io.ReadCloser {
return res.body
}
Loading

0 comments on commit 35a3a02

Please sign in to comment.