Skip to content

Commit ea533fe

Browse files
Support Go fmt-formatted strings
1 parent b6cd819 commit ea533fe

File tree

11 files changed

+569
-74
lines changed

11 files changed

+569
-74
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The goal of project `gexe` is to make it simple to write code for system operati
99
## What can you do with `gexe`?
1010
* Parse and execute OS plain text commands, as you would in a shell.
1111
* Support for variable expansion in command string (i.e. `gexe.Run("echo $HOME")`)
12+
* Support for Go's `fmt.Sprintf` formatting in all string parameters (i.e. `gexe.Run("echo %s", "Hello")`)
1213
* Ability to pipe processes: `gexe.Pipe("cat /etc/hosts", "wc -l")`
1314
* Run processes concurrently: `gexe.RunConcur('wget https://example.com/files'; "date")`
1415
* Get process information (i.e. PID, status, exit code, etc)
@@ -41,6 +42,23 @@ if proc.Err() != nil {
4142
fmt.Println(proc.Result())
4243
```
4344

45+
### String formatting with Go's fmt syntax
46+
`gexe` methods now support Go's `fmt.Sprintf` formatting alongside variable expansion:
47+
48+
```go
49+
// Using Go formatting with variable expansion
50+
gexe.SetVar("name", "Alice")
51+
gexe.Run("echo Hello %s, your home is ${HOME}", "World")
52+
53+
// File operations with formatting
54+
gexe.FileWrite("/tmp/log_%s.txt", time.Now().Format("2006-01-02"))
55+
56+
// Variable setting with formatting
57+
gexe.SetVar("message", "User %s logged in at %s", username, timestamp)
58+
```
59+
60+
The formatting is applied intelligently - if no format verbs are detected in the string, the arguments are ignored, maintaining backward compatibility.
61+
4462
## Examples
4563
Find more examples [here](./examples/)!
4664

echo.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ func (e *Session) AddExecPath(execPath string) {
4444
}
4545

4646
// ProgAvail returns the full path of the program if found on exec PATH
47-
func (e *Session) ProgAvail(progName string) string {
47+
func (e *Session) ProgAvail(progName string, args ...interface{}) string {
48+
progName = applySprintfIfNeeded(progName, args...)
4849
path, err := exec.LookPath(e.Eval(progName))
4950
if err != nil {
5051
return ""

examples/sprintf_demo/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Sprintf Demo
2+
3+
This example demonstrates the new `fmt.Sprintf` functionality in gexe, which allows you to use Go's string formatting alongside variable expansion.
4+
5+
## Features Demonstrated
6+
7+
1. **Basic string formatting**: Using `%s`, `%d`, `%t` format verbs
8+
2. **Multiple arguments**: Passing multiple values for formatting
9+
3. **Combined functionality**: Using both sprintf and variable expansion together
10+
4. **File operations**: Creating files with formatted paths
11+
5. **Variable setting**: Setting variables with formatted values
12+
6. **Backward compatibility**: Showing that unused args are ignored when no format verbs exist
13+
14+
## Run the Example
15+
16+
```bash
17+
go run main.go
18+
```
19+
20+
## Key Benefits
21+
22+
- **Cleaner code**: No need for manual string concatenation or `fmt.Sprintf` calls
23+
- **Type safety**: Go's type checking for format arguments
24+
- **Flexibility**: Combine with existing variable expansion features
25+
- **Backward compatible**: Existing code continues to work unchanged

examples/sprintf_demo/main.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/vladimirvivien/gexe"
8+
)
9+
10+
func main() {
11+
fmt.Println("=== Gexe Sprintf Functionality Demo ===")
12+
13+
// 1. Basic string formatting
14+
fmt.Println("\n1. Basic string formatting:")
15+
result := gexe.Run("echo Hello %s!", "World")
16+
fmt.Printf(" Result: %s\n", result)
17+
18+
// 2. Multiple arguments
19+
fmt.Println("\n2. Multiple arguments:")
20+
result = gexe.Run("echo User: %s, ID: %d, Active: %t", "Alice", 42, true)
21+
fmt.Printf(" Result: %s\n", result)
22+
23+
// 3. Combined with variable expansion
24+
fmt.Println("\n3. Combined with variable expansion:")
25+
gexe.SetVar("greeting", "Welcome")
26+
result = gexe.Run("echo ${greeting} %s to our platform!", "Bob")
27+
fmt.Printf(" Result: %s\n", result)
28+
29+
// 4. File operations with formatting
30+
fmt.Println("\n4. File operations with formatting:")
31+
filename := fmt.Sprintf("demo_%s.txt", time.Now().Format("20060102_150405"))
32+
gexe.FileWrite("/tmp/%s", filename).String("This is a demo file created with sprintf formatting")
33+
34+
if gexe.PathExists("/tmp/%s", filename) {
35+
content := gexe.FileRead("/tmp/%s", filename).String()
36+
fmt.Printf(" Created file: /tmp/%s\n", filename)
37+
fmt.Printf(" Content: %s\n", content)
38+
}
39+
40+
// 5. Variable setting with formatting
41+
fmt.Println("\n5. Variable setting with formatting:")
42+
gexe.SetVar("status", "User %s has %d points", "Charlie", 150)
43+
fmt.Printf(" Variable value: %s\n", gexe.Val("status"))
44+
45+
// 6. Backward compatibility (no formatting verbs)
46+
fmt.Println("\n6. Backward compatibility:")
47+
result = gexe.Run("echo Hello World", "unused_arg", "another_unused")
48+
fmt.Printf(" Result: %s (args ignored when no format verbs)\n", result)
49+
50+
// 7. Complex example: log file creation
51+
fmt.Println("\n7. Complex example - log file creation:")
52+
user := "admin"
53+
action := "login"
54+
timestamp := time.Now().Format("2006-01-02 15:04:05")
55+
56+
gexe.SetVar("log_dir", "/tmp/logs")
57+
gexe.MkDir("${log_dir}", 0755)
58+
59+
logEntry := fmt.Sprintf("[%s] User %s performed %s", timestamp, user, action)
60+
gexe.FileWrite("${log_dir}/app_%s.log", time.Now().Format("20060102")).String(logEntry)
61+
62+
fmt.Printf(" Log entry created: %s\n", logEntry)
63+
64+
// Cleanup
65+
gexe.Run("rm -f /tmp/demo_*.txt /tmp/logs/*.log")
66+
gexe.Run("rmdir /tmp/logs")
67+
68+
fmt.Println("\n=== Demo Complete ===")
69+
}

filesys.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,56 +9,66 @@ import (
99

1010
// PathExists returns true if path exists.
1111
// All errors causes to return false.
12-
func (e *Session) PathExists(path string) bool {
12+
func (e *Session) PathExists(path string, args ...interface{}) bool {
13+
path = applySprintfIfNeeded(path, args...)
1314
return fs.PathWithVars(path, e.vars).Exists()
1415
}
1516

1617
// MkDir creates a directory at specified path with mode value.
1718
// FSInfo contains information about the path or error if occured
18-
func (e *Session) MkDir(path string, mode os.FileMode) *fs.FSInfo {
19+
func (e *Session) MkDir(path string, mode os.FileMode, args ...interface{}) *fs.FSInfo {
20+
path = applySprintfIfNeeded(path, args...)
1921
p := fs.PathWithVars(path, e.vars)
2022
return p.MkDir(mode)
2123
}
2224

2325
// RmPath removes specified path (dir or file).
2426
// Error is returned FSInfo.Err()
25-
func (e *Session) RmPath(path string) *fs.FSInfo {
27+
func (e *Session) RmPath(path string, args ...interface{}) *fs.FSInfo {
28+
path = applySprintfIfNeeded(path, args...)
2629
p := fs.PathWithVars(path, e.vars)
2730
return p.Remove()
2831
}
2932

3033
// PathInfo
31-
func (e *Session) PathInfo(path string) *fs.FSInfo {
34+
func (e *Session) PathInfo(path string, args ...interface{}) *fs.FSInfo {
35+
path = applySprintfIfNeeded(path, args...)
3236
return fs.PathWithVars(path, e.vars).Info()
3337
}
3438

3539
// FileReadWithContext uses specified context to provide methods to read file
3640
// content at path.
37-
func (e *Session) FileReadWithContext(ctx context.Context, path string) *fs.FileReader {
41+
func (e *Session) FileReadWithContext(ctx context.Context, path string, args ...interface{}) *fs.FileReader {
42+
path = applySprintfIfNeeded(path, args...)
3843
return fs.ReadWithContextVars(ctx, path, e.vars)
3944
}
4045

4146
// FileRead provides methods to read file content
42-
func (e *Session) FileRead(path string) *fs.FileReader {
47+
func (e *Session) FileRead(path string, args ...interface{}) *fs.FileReader {
48+
path = applySprintfIfNeeded(path, args...)
4349
return fs.ReadWithContextVars(context.Background(), path, e.vars)
4450
}
4551

4652
// FileWriteWithContext uses context ctx to create a fs.FileWriter to write content to provided path
47-
func (e *Session) FileWriteWithContext(ctx context.Context, path string) *fs.FileWriter {
53+
func (e *Session) FileWriteWithContext(ctx context.Context, path string, args ...interface{}) *fs.FileWriter {
54+
path = applySprintfIfNeeded(path, args...)
4855
return fs.WriteWithContextVars(ctx, path, e.vars)
4956
}
5057

5158
// FileWrite creates a fs.FileWriter to write content to provided path
52-
func (e *Session) FileWrite(path string) *fs.FileWriter {
59+
func (e *Session) FileWrite(path string, args ...interface{}) *fs.FileWriter {
60+
path = applySprintfIfNeeded(path, args...)
5361
return fs.WriteWithContextVars(context.Background(), path, e.vars)
5462
}
5563

5664
// FileAppend creates a new fs.FileWriter to append content to provided path
57-
func (e *Session) FileAppendWithContext(ctx context.Context, path string) *fs.FileWriter {
65+
func (e *Session) FileAppendWithContext(ctx context.Context, path string, args ...interface{}) *fs.FileWriter {
66+
path = applySprintfIfNeeded(path, args...)
5867
return fs.AppendWithContextVars(ctx, path, e.vars)
5968
}
6069

6170
// FileAppend creates a new fs.FileWriter to append content to provided path
62-
func (e *Session) FileAppend(path string) *fs.FileWriter {
71+
func (e *Session) FileAppend(path string, args ...interface{}) *fs.FileWriter {
72+
path = applySprintfIfNeeded(path, args...)
6373
return fs.AppendWithContextVars(context.Background(), path, e.vars)
6474
}

0 commit comments

Comments
 (0)