Skip to content

Commit 6a9140e

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

File tree

13 files changed

+714
-74
lines changed

13 files changed

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

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)