Skip to content

Commit

Permalink
Merge pull request #99 from xushiwei/log
Browse files Browse the repository at this point in the history
x/gsh
  • Loading branch information
xushiwei authored Feb 16, 2024
2 parents 55db357 + ffe6373 commit 24c6a82
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 0 deletions.
135 changes: 135 additions & 0 deletions gsh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
Go+ DevOps Tools
======

[![Language](https://img.shields.io/badge/language-Go+-blue.svg)](https://github.com/goplus/gop)
[![GitHub release](https://img.shields.io/github/v/tag/goplus/gop.svg?label=Go%2b+release)](https://github.com/goplus/gop/releases)
[![Discord](https://img.shields.io/badge/Discord-online-success.svg?logo=discord&logoColor=white)](https://discord.gg/mYjWCJDcAr)
[![GoDoc](https://pkg.go.dev/badge/github.com/qiniu/x/gsh.svg)](https://pkg.go.dev/github.com/qiniu/x/gsh)

This is an alternative to writing shell scripts.

Yes, now you can write `shell script` in Go+. It supports all shell commands.

## Usage

First, let's create a Go+ source file named `./example.gsh` and write the following code:

```coffee
mkdir "testgsh"
```

Then run it:

```sh
gop run example.gsh
```

It's strange to you that the file extension of Go+ source is not `.gop` but `.gsh`. It is only because Go+ register `.gsh` as a builtin [classfile](https://github.com/goplus/gop/blob/main/doc/classfile.md).

We can change `./example.gsh` more complicated:

```coffee
type file struct {
name string
fsize int
}

mkdir! "testgsh"

mkdir "testgsh2"
lastErr!

mkdir "testgsh3"
if lastErr != nil {
panic lastErr
}

capout => { ls }
println output.fields

capout => { ls "-l" }
files := [file{flds[8], flds[4].int!} for e <- output.split("\n") if flds := e.fields; flds.len > 2]
println files

rmdir "testgsh", "testgsh2", "testgsh3"
```

### Check last error

If we want to ensure `mkdir` successfully, there are three ways:

The simplest way is:

```coffee
mkdir! "testsh" # will panic if mkdir failed
```

The second way is:

```coffee
mkdir "testsh"
lastErr!
```

Yes, `gsh` provides `lastErr` to check last error.

The third way is:

```coffee
mkdir "testsh"
if lastErr != nil {
panic lastErr
}
```

This is the most familiar way to Go developers.

### Capture output of commands

And, `gsh` provides a way to capture output of commands:

```coffee
capout => {
...
}
```

Similar to `lastErr`, the captured output result is saved to `output`.

For an example:

```coffee
capout => { ls "-l" }
println output
```

Here is a possible output:

```s
total 72
-rw-r--r-- 1 xushiwei staff 11357 Jun 19 00:20 LICENSE
-rw-r--r-- 1 xushiwei staff 127 Jun 19 10:00 README.md
-rw-r--r-- 1 xushiwei staff 365 Jun 19 00:25 example.gsh
-rw-r--r-- 1 xushiwei staff 126 Jun 19 09:33 go.mod
-rw-r--r-- 1 xushiwei staff 165 Jun 19 09:33 go.sum
-rw-r--r-- 1 xushiwei staff 1938 Jun 19 10:00 gop_autogen.go
```

We can use [Go+ powerful built-in data processing capabilities](doc/docs.md#data-processing) to process captured `output`:

```coffee
type file struct {
name string
fsize int
}

files := [file{flds[8], flds[4].int!} for e <- output.split("\n") if flds := e.fields; flds.len > 2]
```

In this example, we split `output` by `"\n"`, and for each entry `e`, split it by spaces (`e.fields`) and save into `flds`. Condition `flds.len > 2` is to remove special line of output:

```s
total 72
```

At last, pick file name and size of all selected entries and save into `files`.
78 changes: 78 additions & 0 deletions gsh/classfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
Copyright 2024 Qiniu Limited (qiniu.com)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gsh

import (
"bytes"
"io"
"os"
"os/exec"
)

// App is project class of this classfile.
type App struct {
fout io.Writer
ferr io.Writer
fin io.Reader
cout string
err error
}

func (p *App) initApp() {
p.fin = os.Stdin
p.fout = os.Stdout
p.ferr = os.Stderr
}

// Gop_Exec executes a shell command.
func (p *App) Gop_Exec(name string, args ...string) error {
cmd := exec.Command(name, args...)
cmd.Stdin = p.fin
cmd.Stdout = p.fout
cmd.Stderr = p.ferr
p.err = cmd.Run()
return p.err
}

// LastErr returns error of last command execution.
func (p *App) LastErr() error {
return p.err
}

// Capout captures stdout of doSth() execution and save it to output.
func (p *App) Capout(doSth func()) (string, error) {
var out bytes.Buffer
old := p.fout
p.fout = &out
defer func() {
p.fout = old
}()
doSth()
p.cout = out.String()
return p.cout, p.err
}

// Output returns result of last capout.
func (p *App) Output() string {
return p.cout
}

// Gopt_App_Main is main entry of this classfile.
func Gopt_App_Main(a interface{ initApp() }) {
a.initApp()
a.(interface{ MainEntry() }).MainEntry()
}

0 comments on commit 24c6a82

Please sign in to comment.