utils API

utils

package

API reference for the utils package.

F
function

TestOpenBrowserError

Parameters

cmd/rfw/utils/network_test.go:8-16
func TestOpenBrowserError(t *testing.T)

{
	orig := os.Getenv("BROWSER")
	_ = os.Setenv("BROWSER", "nonexistent-browser")
	defer os.Setenv("BROWSER", orig)

	if err := OpenBrowser("http://example.com"); err == nil {
		t.Fatalf("expected error when browser command is missing")
	}
}
F
function

ClearScreen

cmd/rfw/utils/output.go:26-29
func ClearScreen()

{
	fmt.Print("\033[H\033[2J")
	fmt.Println()
}
F
function

PrintStartupInfo

Parameters

port
string
httpsPort
string
localIP
string
host
bool
cmd/rfw/utils/output.go:31-44
func PrintStartupInfo(port, httpsPort, localIP string, host bool)

{
	fmt.Println(indent, boldRed("rfw"), faint(core.Version))
	fmt.Println()
	fmt.Println(indent, red("➜ "), bold("Local:"), red(fmt.Sprintf("http://localhost:%s/ - https://localhost:%s/", port, httpsPort)))

	if host {
		fmt.Println(indent, red("➜ "), faint(bold("Network:")), white(fmt.Sprintf("http://%s:%s/ (https://%s:%s/)", localIP, port, localIP, httpsPort)))
	} else {
		fmt.Println(indent, red("➜ "), faint(bold("Network:")), white("--host"), faint("to expose"))
	}

	fmt.Println(indent, faintRed("➜ "), faint("Press"), bold("h + enter"), faint("to show help"))
	fmt.Println()
}
F
function

LogServeRequest

Parameters

cmd/rfw/utils/output.go:46-48
func LogServeRequest(r *http.Request)

{
	fmt.Printf("%s %s %s\n", faint(time.Now().Format("15:04:05")), boldYellow("serving"), faint(r.URL.Path))
}
F
function

Info

Parameters

message
string
cmd/rfw/utils/output.go:50-52
func Info(message string)

{
	fmt.Println(boldRed("[rfw]"), message)
}
F
function

Fatal

Parameters

message
string
err
error
cmd/rfw/utils/output.go:54-56
func Fatal(message string, err error)

{
	log.Fatalf(boldRed("[rfw] "), message, err)
}
F
function

EnableDebug

Parameters

d
bool
cmd/rfw/utils/output.go:58-58
func EnableDebug(d bool)

{ dbg = d }
F
function

IsDebug

IsDebug reports whether debug mode is enabled.

Returns

bool
cmd/rfw/utils/output.go:61-61
func IsDebug() bool

{ return dbg }
F
function

Debug

Parameters

message
string
cmd/rfw/utils/output.go:63-67
func Debug(message string)

{
	if dbg {
		fmt.Println(boldRed("[rfw][debug]"), faint(message))
	}
}
F
function

PrintHelp

cmd/rfw/utils/output.go:69-82
func PrintHelp()

{
	ClearScreen()
	fmt.Println()
	fmt.Println(indent, red("➜ "), bold("Help"))
	fmt.Println(indent, indent, yellow("➜ "), bold("Shortcuts"))
	fmt.Println(indent, indent, indent, faint("Press"), bold("c + enter"), faint("to stop the server"))
	fmt.Println(indent, indent, indent, faint("Press"), bold("o + enter"), faint("to open the browser"))
	fmt.Println(indent, indent, indent, faint("Press"), bold("u + enter"), faint("to show the startup info and clear logs"))
	fmt.Println(indent, indent, indent, faint("Press"), bold("h + enter"), faint("to show this help"))
	fmt.Println(indent, indent, yellow("➜ "), bold("Flags"))
	fmt.Println(indent, indent, indent, faint("Use"), bold("--host"), faint("to expose the server to the network"))
	fmt.Println(indent, indent, indent, faint("Use"), bold("--port=XXXX"), faint("to specify a port"))
	fmt.Println()
}
S
struct

githubRelease

cmd/rfw/utils/update.go:27-33
type githubRelease struct

Fields

Name Type Description
TagName string json:"tag_name"
Assets []struct { BrowserDownloadURL string `json:"browser_download_url"` Name string `json:"name"` } json:"assets"
F
function

fetchLatestVersion

Returns

string
error
cmd/rfw/utils/update.go:35-70
func fetchLatestVersion() (string, error)

{
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/repos/"+githubRepo+"/releases/latest", nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Accept", "application/vnd.github+json")

	client := &http.Client{Transport: &http.Transport{
		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			return net.DialTimeout(network, addr, 3*time.Second)
		},
	}}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return "", fmt.Errorf("status %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var release githubRelease
	if err := json.Unmarshal(body, &release); err != nil {
		return "", err
	}
	return release.TagName, nil
}
F
function

shouldCheckUpdate

Returns

bool
cmd/rfw/utils/update.go:72-83
func shouldCheckUpdate() bool

{
	home, err := os.UserHomeDir()
	if err != nil {
		return true
	}
	path := home + "/" + checkFile
	info, err := os.Stat(path)
	if err != nil {
		return true
	}
	return time.Since(info.ModTime()) > checkInterval
}
F
function

markChecked

cmd/rfw/utils/update.go:85-92
func markChecked()

{
	home, err := os.UserHomeDir()
	if err != nil {
		return
	}
	path := home + "/" + checkFile
	os.WriteFile(path, []byte(time.Now().Format(time.RFC3339)), 0644)
}
F
function

isNewer

Parameters

current
string
latest
string

Returns

bool
cmd/rfw/utils/update.go:94-114
func isNewer(current, latest string) bool

{
	c := strings.TrimPrefix(current, "v")
	l := strings.TrimPrefix(latest, "v")
	if c == "" || l == "" {
		return false
	}
	partsC := strings.SplitN(c, ".", 3)
	partsL := strings.SplitN(l, ".", 3)
	for i := 0; i < 3; i++ {
		if i >= len(partsC) || i >= len(partsL) {
			break
		}
		if partsL[i] > partsC[i] {
			return true
		}
		if partsL[i] < partsC[i] {
			return false
		}
	}
	return false
}
F
function

downloadAndReplace

Parameters

assetURL
string

Returns

error
cmd/rfw/utils/update.go:116-160
func downloadAndReplace(assetURL string) error

{
	tmp, err := os.CreateTemp("", "rfw-update-*")
	if err != nil {
		return err
	}
	tmp.Close()
	defer os.Remove(tmp.Name())

	ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
	defer cancel()

	req, err := http.NewRequestWithContext(ctx, "GET", assetURL, nil)
	if err != nil {
		return err
	}

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return fmt.Errorf("download failed: status %d", resp.StatusCode)
	}

	if _, err := io.Copy(tmp, resp.Body); err != nil {
		return err
	}

	exePath, err := os.Executable()
	if err != nil {
		return err
	}

	if err := os.Chmod(tmp.Name(), 0755); err != nil {
		return err
	}

	if err := os.Rename(tmp.Name(), exePath); err != nil {
		return os.WriteFile(exePath, mustReadFile(tmp.Name()), 0755)
	}

	return nil
}
F
function

mustReadFile

Parameters

path
string

Returns

[]byte
cmd/rfw/utils/update.go:162-170
func mustReadFile(path string) []byte

{
	f, err := os.Open(path)
	if err != nil {
		return nil
	}
	defer f.Close()
	data, _ := io.ReadAll(f)
	return data
}
F
function

getAssetName

Returns

string
cmd/rfw/utils/update.go:172-180
func getAssetName() string

{
	goos := runtime.GOOS
	goarch := runtime.GOARCH
	ext := ""
	if goos == "windows" {
		ext = ".exe"
	}
	return fmt.Sprintf("rfw-%s-%s%s", goos, goarch, ext)
}
F
function

CheckForUpdate

cmd/rfw/utils/update.go:182-244
func CheckForUpdate()

{
	if !shouldCheckUpdate() {
		return
	}

	latest, err := fetchLatestVersion()
	if err != nil {
		return
	}
	markChecked()

	if !isNewer(core.Version, latest) {
		return
	}

	assetName := getAssetName()
	fmt.Println()
	Info(fmt.Sprintf("Update available: %s → %s", faint(core.Version), boldCyan(latest)))

	fmt.Print(indent, red("➜ "), bold("Update now? [y/N] "))
	var answer string
	fmt.Scanln(&answer)
	answer = strings.TrimSpace(strings.ToLower(answer))
	if answer != "y" && answer != "yes" {
		fmt.Println(indent, faint("Skipped."))
		return
	}

	assetURL := ""
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/repos/"+githubRepo+"/releases/latest", nil)
	req.Header.Set("Accept", "application/vnd.github+json")
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		Info("Failed to fetch release info")
		return
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	var release githubRelease
	json.Unmarshal(body, &release)

	for _, a := range release.Assets {
		if a.Name == assetName {
			assetURL = a.BrowserDownloadURL
			break
		}
	}
	if assetURL == "" {
		Info(fmt.Sprintf("No binary found for %s/%s", runtime.GOOS, runtime.GOARCH))
		return
	}

	Info("Downloading...")
	if err := downloadAndReplace(assetURL); err != nil {
		Info(fmt.Sprintf("Update failed: %v", err))
		return
	}

	Info(fmt.Sprintf("Updated to %s!", latest))
}
F
function

captureOutput

captureOutput redirects stdout for the duration of f and returns what was
written to it.

Parameters

f
func()

Returns

string
cmd/rfw/utils/utils_test.go:14-24
func captureOutput(f func()) string

{
	orig := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w
	f()
	w.Close()
	os.Stdout = orig
	var buf bytes.Buffer
	_, _ = io.Copy(&buf, r)
	return buf.String()
}
F
function

TestDebug

Parameters

cmd/rfw/utils/utils_test.go:26-38
func TestDebug(t *testing.T)

{
	EnableDebug(true)
	out := captureOutput(func() { Debug("hello") })
	if !strings.Contains(out, "[rfw][debug]") {
		t.Fatalf("expected debug output, got %q", out)
	}

	EnableDebug(false)
	out = captureOutput(func() { Debug("no output") })
	if out != "" {
		t.Fatalf("expected no output, got %q", out)
	}
}
F
function

TestIsDebug

Parameters

cmd/rfw/utils/utils_test.go:40-49
func TestIsDebug(t *testing.T)

{
	EnableDebug(true)
	if !IsDebug() {
		t.Fatalf("expected true in debug mode")
	}
	EnableDebug(false)
	if IsDebug() {
		t.Fatalf("expected false when debug disabled")
	}
}
F
function

TestPrintStartupInfo

Parameters

cmd/rfw/utils/utils_test.go:51-63
func TestPrintStartupInfo(t *testing.T)

{
	out := captureOutput(func() { PrintStartupInfo("8080", "8443", "192.168.0.1", true) })
	if !strings.Contains(out, "http://localhost:8080/") {
		t.Fatalf("expected local URL in output, got %q", out)
	}
	if !strings.Contains(out, "http://192.168.0.1:8080/") {
		t.Fatalf("expected network URL, got %q", out)
	}
	out = captureOutput(func() { PrintStartupInfo("8080", "8443", "", false) })
	if !strings.Contains(out, "--host") {
		t.Fatalf("expected hint about --host, got %q", out)
	}
}
F
function

TestPrintHelp

Parameters

cmd/rfw/utils/utils_test.go:65-70
func TestPrintHelp(t *testing.T)

{
	out := captureOutput(PrintHelp)
	if !strings.Contains(out, "Shortcuts") || !strings.Contains(out, "Flags") {
		t.Fatalf("missing help sections, got %q", out)
	}
}
F
function

TestLogServeRequest

Parameters

cmd/rfw/utils/utils_test.go:72-78
func TestLogServeRequest(t *testing.T)

{
	req := httptest.NewRequest("GET", "/foo", nil)
	out := captureOutput(func() { LogServeRequest(req) })
	if !strings.Contains(out, "/foo") {
		t.Fatalf("expected path in output, got %q", out)
	}
}
F
function

GetLocalIP

Returns

string
error
cmd/rfw/utils/network.go:13-26
func GetLocalIP() (string, error)

{
	addrs, err := net.InterfaceAddrs()
	if err != nil {
		return "", err
	}

	for _, addr := range addrs {
		if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() && ipNet.IP.To4() != nil {
			return ipNet.IP.String(), nil
		}
	}

	return "", fmt.Errorf("no local IP address found")
}
F
function

OpenBrowser

Parameters

url
string

Returns

error
cmd/rfw/utils/network.go:28-36
func OpenBrowser(url string) error

{
	if configured := os.Getenv("BROWSER"); configured != "" {
		cmd := strings.Fields(configured)[0]
		if _, err := exec.LookPath(cmd); err != nil {
			return err
		}
	}
	return browser.OpenURL(url)
}