modernize: update GitHub Actions and modernize Go code with latest stdlib features

## CHANGES

- Upgrade GitHub Actions to latest versions (v6, v21)
- Add modernization check step in CI workflow
- Replace strings manipulation with `strings.CutPrefix` and `strings.CutSuffix`
- Replace manual loops with `slices.Contains` for validation
- Use `strings.SplitSeq` for iterator-based string splitting
- Replace `bytes.TrimPrefix` with `bytes.CutPrefix` for clarity
- Use `strings.Builder` instead of string concatenation
- Replace `fmt.Sprintf` with `fmt.Appendf` for efficiency
- Simplify padding calculation with `max` builtin
This commit is contained in:
Kayvan Sylvan
2025-12-15 23:55:37 -08:00
parent 57c3e36574
commit fdadeae1e7
16 changed files with 69 additions and 96 deletions

View File

@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"reflect"
"slices"
"strconv"
"strings"
@@ -230,8 +231,8 @@ func parseDebugLevel(args []string) int {
if lvl, err := strconv.Atoi(args[i+1]); err == nil {
return lvl
}
} else if strings.HasPrefix(arg, "--debug=") {
if lvl, err := strconv.Atoi(strings.TrimPrefix(arg, "--debug=")); err == nil {
} else if after, ok := strings.CutPrefix(arg, "--debug="); ok {
if lvl, err := strconv.Atoi(after); err == nil {
return lvl
}
}
@@ -241,8 +242,8 @@ func parseDebugLevel(args []string) int {
func extractFlag(arg string) string {
var flag string
if strings.HasPrefix(arg, "--") {
flag = strings.TrimPrefix(arg, "--")
if after, ok := strings.CutPrefix(arg, "--"); ok {
flag = after
if i := strings.Index(flag, "="); i > 0 {
flag = flag[:i]
}
@@ -348,10 +349,8 @@ func validateImageFile(imagePath string) error {
ext := strings.ToLower(filepath.Ext(imagePath))
validExtensions := []string{".png", ".jpeg", ".jpg", ".webp"}
for _, validExt := range validExtensions {
if ext == validExt {
return nil // Valid extension found
}
if slices.Contains(validExtensions, ext) {
return nil // Valid extension found
}
return fmt.Errorf("%s", fmt.Sprintf(i18n.T("invalid_image_file_extension"), ext))
@@ -370,13 +369,7 @@ func validateImageParameters(imagePath, size, quality, background string, compre
// Validate size
if size != "" {
validSizes := []string{"1024x1024", "1536x1024", "1024x1536", "auto"}
valid := false
for _, validSize := range validSizes {
if size == validSize {
valid = true
break
}
}
valid := slices.Contains(validSizes, size)
if !valid {
return fmt.Errorf("%s", fmt.Sprintf(i18n.T("invalid_image_size"), size))
}
@@ -385,13 +378,7 @@ func validateImageParameters(imagePath, size, quality, background string, compre
// Validate quality
if quality != "" {
validQualities := []string{"low", "medium", "high", "auto"}
valid := false
for _, validQuality := range validQualities {
if quality == validQuality {
valid = true
break
}
}
valid := slices.Contains(validQualities, quality)
if !valid {
return fmt.Errorf("%s", fmt.Sprintf(i18n.T("invalid_image_quality"), quality))
}
@@ -400,13 +387,7 @@ func validateImageParameters(imagePath, size, quality, background string, compre
// Validate background
if background != "" {
validBackgrounds := []string{"opaque", "transparent"}
valid := false
for _, validBackground := range validBackgrounds {
if background == validBackground {
valid = true
break
}
}
valid := slices.Contains(validBackgrounds, background)
if !valid {
return fmt.Errorf("%s", fmt.Sprintf(i18n.T("invalid_image_background"), background))
}

View File

@@ -183,10 +183,10 @@ func detectLanguageFromArgs() string {
if i+1 < len(args) {
return args[i+1]
}
} else if strings.HasPrefix(arg, "--language=") {
return strings.TrimPrefix(arg, "--language=")
} else if strings.HasPrefix(arg, "-g=") {
return strings.TrimPrefix(arg, "-g=")
} else if after, ok := strings.CutPrefix(arg, "--language="); ok {
return after
} else if after, ok := strings.CutPrefix(arg, "-g="); ok {
return after
} else if runtime.GOOS == "windows" && strings.HasPrefix(arg, "/g:") {
return strings.TrimPrefix(arg, "/g:")
} else if runtime.GOOS == "windows" && strings.HasPrefix(arg, "/g=") {
@@ -272,10 +272,7 @@ func (h *TranslatedHelpWriter) writeAllFlags() {
// Pad to align descriptions
flagStr := flagLine.String()
padding := 34 - len(flagStr)
if padding < 2 {
padding = 2
}
padding := max(34-len(flagStr), 2)
fmt.Fprintf(h.writer, "%s%s%s", flagStr, strings.Repeat(" ", padding), description)

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/atotto/clipboard"
@@ -66,10 +67,5 @@ func CreateAudioOutputFile(audioData []byte, fileName string) (err error) {
func IsAudioFormat(fileName string) bool {
ext := strings.ToLower(filepath.Ext(fileName))
audioExts := []string{".wav", ".mp3", ".m4a", ".aac", ".ogg", ".flac"}
for _, audioExt := range audioExts {
if ext == audioExt {
return true
}
}
return false
return slices.Contains(audioExts, ext)
}

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
)
@@ -146,14 +147,7 @@ func fixInvalidEscapes(jsonStr string) string {
// Check for escape sequences only inside strings
if inQuotes && ch == '\\' && i+1 < len(jsonStr) {
nextChar := jsonStr[i+1]
isValid := false
for _, validEscape := range validEscapes {
if nextChar == validEscape {
isValid = true
break
}
}
isValid := slices.Contains(validEscapes, nextChar)
if !isValid {
// Invalid escape sequence - add an extra backslash

View File

@@ -3,6 +3,7 @@ package gemini
import (
"fmt"
"sort"
"strings"
)
// GeminiVoice represents a Gemini TTS voice with its characteristics
@@ -126,16 +127,17 @@ func ListGeminiVoices(shellCompleteMode bool) string {
if shellCompleteMode {
// For shell completion, just return voice names
names := GetGeminiVoiceNames()
result := ""
var result strings.Builder
for _, name := range names {
result += name + "\n"
result.WriteString(name + "\n")
}
return result
return result.String()
}
// For human-readable output
voices := GetGeminiVoices()
result := "Available Gemini Text-to-Speech voices:\n\n"
var result strings.Builder
result.WriteString("Available Gemini Text-to-Speech voices:\n\n")
// Group by characteristics for better readability
groups := map[string][]GeminiVoice{
@@ -186,22 +188,22 @@ func ListGeminiVoices(shellCompleteMode bool) string {
// Output grouped voices
for groupName, groupVoices := range groups {
if len(groupVoices) > 0 {
result += fmt.Sprintf("%s:\n", groupName)
result.WriteString(fmt.Sprintf("%s:\n", groupName))
for _, voice := range groupVoices {
defaultStr := ""
if voice.Name == "Kore" {
defaultStr = " (default)"
}
result += fmt.Sprintf(" %-15s - %s%s\n", voice.Name, voice.Description, defaultStr)
result.WriteString(fmt.Sprintf(" %-15s - %s%s\n", voice.Name, voice.Description, defaultStr))
}
result += "\n"
result.WriteString("\n")
}
}
result += "Use --voice <voice_name> to select a specific voice.\n"
result += "Example: fabric --voice Charon -m gemini-2.5-flash-preview-tts -o output.wav \"Hello world\"\n"
result.WriteString("Use --voice <voice_name> to select a specific voice.\n")
result.WriteString("Example: fabric --voice Charon -m gemini-2.5-flash-preview-tts -o output.wav \"Hello world\"\n")
return result
return result.String()
}
// NOTE: This implementation maintains a curated list based on official Google documentation.

View File

@@ -140,8 +140,8 @@ func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.Cha
continue
}
if bytes.HasPrefix(line, []byte("data: ")) {
line = bytes.TrimPrefix(line, []byte("data: "))
if after, ok := bytes.CutPrefix(line, []byte("data: ")); ok {
line = after
}
if string(line) == "[DONE]" {

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"github.com/danielmiessler/fabric/internal/domain"
@@ -31,12 +32,7 @@ var ImageGenerationSupportedModels = []string{
// supportsImageGeneration checks if the given model supports the image_generation tool
func supportsImageGeneration(model string) bool {
for _, supportedModel := range ImageGenerationSupportedModels {
if model == supportedModel {
return true
}
}
return false
return slices.Contains(ImageGenerationSupportedModels, model)
}
// getOutputFormatFromExtension determines the API output format based on file extension

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"os"
"strings"
"sync"
"github.com/danielmiessler/fabric/internal/domain"
@@ -107,18 +108,19 @@ func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, o
return "", fmt.Errorf("perplexity API request failed: %w", err) // Corrected capitalization
}
content := resp.GetLastContent()
var content strings.Builder
content.WriteString(resp.GetLastContent())
// Append citations if available
citations := resp.GetCitations()
if len(citations) > 0 {
content += "\n\n# CITATIONS\n\n"
content.WriteString("\n\n# CITATIONS\n\n")
for i, citation := range citations {
content += fmt.Sprintf("- [%d] %s\n", i+1, citation)
content.WriteString(fmt.Sprintf("- [%d] %s\n", i+1, citation))
}
}
return content, nil
return content.String(), nil
}
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan string) error {

View File

@@ -189,7 +189,8 @@ esac`
// Helper function to create and register extension
createExtension := func(name, opName, cmdTemplate string, config map[string]any) error {
configPath := filepath.Join(tmpDir, name+".yaml")
configContent := `name: ` + name + `
var configContent strings.Builder
configContent.WriteString(`name: ` + name + `
executable: ` + testScript + `
type: executable
timeout: 30s
@@ -199,14 +200,14 @@ operations:
config:
output:
method: file
file_config:`
file_config:`)
// Add config options
for k, v := range config {
configContent += "\n " + k + ": " + strings.TrimSpace(v.(string))
configContent.WriteString("\n " + k + ": " + strings.TrimSpace(v.(string)))
}
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
if err := os.WriteFile(configPath, []byte(configContent.String()), 0644); err != nil {
return err
}

View File

@@ -102,7 +102,7 @@ func ServeOllama(registry *core.PluginRegistry, address string, version string)
// Ollama Endpoints
r.GET("/api/tags", typeConversion.ollamaTags)
r.GET("/api/version", func(c *gin.Context) {
c.Data(200, "application/json", []byte(fmt.Sprintf("{\"%s\"}", version)))
c.Data(200, "application/json", fmt.Appendf(nil, "{\"%s\"}", version))
})
r.POST("/api/chat", typeConversion.ollamaChat)
@@ -224,7 +224,7 @@ func (f APIConvert) ollamaChat(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"error": "testing endpoint"})
return
}
for _, word := range strings.Split(fabricResponse.Content, " ") {
for word := range strings.SplitSeq(fabricResponse.Content, " ") {
forwardedResponse = OllamaResponse{
Model: "",
CreatedAt: "",