oh-my-posh/src/platform/shell.go

1005 lines
22 KiB
Go
Raw Normal View History

2022-11-09 11:27:54 -08:00
package platform
2019-03-13 04:14:30 -07:00
import (
2021-12-19 07:42:39 -08:00
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
2022-02-25 03:51:38 -08:00
"io"
"io/fs"
"net/http"
2022-07-17 12:11:23 -07:00
"net/http/httputil"
2019-03-13 04:14:30 -07:00
"os"
"os/exec"
2020-10-01 11:57:02 -07:00
"path/filepath"
2019-03-13 04:14:30 -07:00
"runtime"
"strconv"
2019-03-13 04:14:30 -07:00
"strings"
"sync"
"time"
2019-03-13 04:14:30 -07:00
"github.com/jandedobbeleer/oh-my-posh/src/log"
"github.com/jandedobbeleer/oh-my-posh/src/platform/battery"
"github.com/jandedobbeleer/oh-my-posh/src/platform/cmd"
"github.com/jandedobbeleer/oh-my-posh/src/regex"
2022-12-28 08:30:48 -08:00
cpu "github.com/shirou/gopsutil/v3/cpu"
disk "github.com/shirou/gopsutil/v3/disk"
load "github.com/shirou/gopsutil/v3/load"
process "github.com/shirou/gopsutil/v3/process"
2019-03-13 04:14:30 -07:00
)
const (
2022-08-02 22:25:59 -07:00
UNKNOWN = "unknown"
WINDOWS = "windows"
DARWIN = "darwin"
LINUX = "linux"
)
func pid() string {
pid := os.Getenv("POSH_PID")
if len(pid) == 0 {
pid = strconv.Itoa(os.Getppid())
}
return pid
}
var (
TEMPLATECACHE = fmt.Sprintf("template_cache_%s", pid())
TOGGLECACHE = fmt.Sprintf("toggle_cache_%s", pid())
PROMPTCOUNTCACHE = fmt.Sprintf("prompt_count_cache_%s", pid())
)
2022-03-12 13:04:08 -08:00
type Flags struct {
ErrorCode int
Config string
Shell string
ShellVersion string
2022-03-12 13:04:08 -08:00
PWD string
PSWD string
ExecutionTime float64
Eval bool
StackCount int
Migrate bool
TerminalWidth int
Strict bool
Debug bool
Manual bool
Plain bool
Primary bool
PromptCount int
2023-02-05 00:06:26 -08:00
Cleared bool
2023-03-03 11:21:35 -08:00
Version string
}
type CommandError struct {
Err string
ExitCode int
}
func (e *CommandError) Error() string {
return e.Err
}
type FileInfo struct {
ParentFolder string
Path string
IsDir bool
}
type Cache interface {
Init(home string)
Close()
2023-03-03 11:21:35 -08:00
// Gets the value for a given key.
// Returns the value and a boolean indicating if the key was found.
// In case the ttl expired, the function returns false.
Get(key string) (string, bool)
2023-03-03 11:21:35 -08:00
// Sets a value for a given key.
// The ttl indicates how may minutes to cache the value.
Set(key, value string, ttl int)
2021-09-21 11:22:59 -07:00
}
2021-12-14 23:49:32 -08:00
type HTTPRequestModifier func(request *http.Request)
2022-06-01 00:50:20 -07:00
type WindowsRegistryValueType string
const (
2022-06-01 00:50:20 -07:00
DWORD = "DWORD"
QWORD = "QWORD"
BINARY = "BINARY"
STRING = "STRING"
)
type WindowsRegistryValue struct {
ValueType WindowsRegistryValueType
2022-06-01 00:50:20 -07:00
DWord uint64
QWord uint64
String string
}
type NotImplemented struct{}
func (n *NotImplemented) Error() string {
return "not implemented"
}
type ConnectionType string
const (
ETHERNET ConnectionType = "ethernet"
WIFI ConnectionType = "wifi"
CELLULAR ConnectionType = "cellular"
BLUETOOTH ConnectionType = "bluetooth"
)
type Connection struct {
Name string
Type ConnectionType
TransmitRate uint64
ReceiveRate uint64
SSID string // Wi-Fi only
}
type Memory struct {
PhysicalTotalMemory uint64
PhysicalAvailableMemory uint64
PhysicalFreeMemory uint64
PhysicalPercentUsed float64
SwapTotalMemory uint64
SwapFreeMemory uint64
SwapPercentUsed float64
}
type SystemInfo struct {
// mem
Memory
// cpu
Times float64
CPU []cpu.InfoStat
// load
Load1 float64
Load5 float64
Load15 float64
// disk
Disks map[string]disk.IOCountersStat
}
type SegmentsCache map[string]interface{}
func (s *SegmentsCache) Contains(key string) bool {
_, ok := (*s)[key]
return ok
}
type TemplateCache struct {
Root bool
PWD string
Folder string
Shell string
ShellVersion string
UserName string
HostName string
Code int
Env map[string]string
2023-02-19 11:21:33 -08:00
Var map[string]interface{}
OS string
WSL bool
PromptCount int
Segments SegmentsCache
sync.RWMutex
}
func (t *TemplateCache) AddSegmentData(key string, value interface{}) {
t.Lock()
t.Segments[key] = value
t.Unlock()
}
func (t *TemplateCache) RemoveSegmentData(key string) {
t.Lock()
delete(t.Segments, key)
t.Unlock()
}
2022-01-01 11:09:52 -08:00
type Environment interface {
Getenv(key string) string
Pwd() string
Home() string
User() string
Root() bool
Host() (string, error)
GOOS() string
Shell() string
Platform() string
ErrorCode() int
2022-02-19 10:04:06 -08:00
PathSeparator() string
HasFiles(pattern string) bool
HasFilesInDir(dir, pattern string) bool
HasFolder(folder string) bool
HasParentFilePath(path string) (fileInfo *FileInfo, err error)
2022-02-13 23:41:33 -08:00
HasFileInParentDirs(pattern string, depth uint) bool
ResolveSymlink(path string) (string, error)
DirMatchesOneOf(dir string, regexes []string) bool
DirIsWritable(path string) bool
CommandPath(command string) string
HasCommand(command string) bool
FileContent(file string) string
LsDir(path string) []fs.DirEntry
RunCommand(command string, args ...string) (string, error)
RunShellCommand(shell, command string) string
ExecutionTime() float64
2022-03-12 13:04:08 -08:00
Flags() *Flags
BatteryState() (*battery.Info, error)
QueryWindowTitles(processName, windowTitleRegex string) (string, error)
WindowsRegistryKeyValue(path string) (*WindowsRegistryValue, error)
2022-07-17 12:11:23 -07:00
HTTPRequest(url string, body io.Reader, timeout int, requestModifiers ...HTTPRequestModifier) ([]byte, error)
IsWsl() bool
IsWsl2() bool
StackCount() int
TerminalWidth() (int, error)
CachePath() string
Cache() Cache
Close()
Logs() string
InWSLSharedDrive() bool
ConvertToLinuxPath(path string) string
ConvertToWindowsPath(path string) string
Connection(connectionType ConnectionType) (*Connection, error)
TemplateCache() *TemplateCache
LoadTemplateCache()
SetPromptCount()
CursorPosition() (row, col int)
SystemInfo() (*SystemInfo, error)
2023-01-16 11:58:43 -08:00
Debug(message string)
Error(err error)
Trace(start time.Time, args ...string)
2019-03-13 04:14:30 -07:00
}
type commandCache struct {
commands *ConcurrentMap
}
func (c *commandCache) set(command, path string) {
c.commands.Set(command, path)
}
func (c *commandCache) get(command string) (string, bool) {
cacheCommand, found := c.commands.Get(command)
2021-09-21 22:53:59 -07:00
if !found {
return "", false
}
command, ok := cacheCommand.(string)
2021-09-23 13:57:38 -07:00
return command, ok
}
2022-11-09 11:27:54 -08:00
type Shell struct {
CmdFlags *Flags
2023-02-19 11:21:33 -08:00
Var map[string]interface{}
cwd string
cmdCache *commandCache
fileCache *fileCache
tmplCache *TemplateCache
networks []*Connection
sync.RWMutex
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Init() {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2022-03-12 13:04:08 -08:00
if env.CmdFlags == nil {
env.CmdFlags = &Flags{}
}
if env.CmdFlags.Debug {
log.Enable()
}
2022-01-18 12:25:18 -08:00
env.fileCache = &fileCache{}
env.fileCache.Init(env.CachePath())
env.resolveConfigPath()
2021-11-16 22:16:43 -08:00
env.cmdCache = &commandCache{
commands: NewConcurrentMap(),
}
env.SetPromptCount()
}
2022-11-09 11:27:54 -08:00
func (env *Shell) resolveConfigPath() {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
if len(env.CmdFlags.Config) == 0 {
env.CmdFlags.Config = env.Getenv("POSH_THEME")
2022-02-25 03:51:38 -08:00
}
if len(env.CmdFlags.Config) == 0 {
env.Debug("No config set, fallback to default config")
2022-10-12 05:59:37 -07:00
return
}
if strings.HasPrefix(env.CmdFlags.Config, "https://") {
if err := env.downloadConfig(env.CmdFlags.Config); err != nil {
// make it use default config when download fails
env.Error(err)
env.CmdFlags.Config = ""
return
}
2022-02-25 03:51:38 -08:00
}
2022-01-13 23:39:08 -08:00
// Cygwin path always needs the full path as we're on Windows but not really.
// Doing filepath actions will convert it to a Windows path and break the init script.
2022-08-02 22:25:59 -07:00
if env.Platform() == WINDOWS && env.Shell() == "bash" {
env.Debug("Cygwin detected, using full path for config")
2022-01-13 23:39:08 -08:00
return
}
2022-03-12 13:04:08 -08:00
configFile := env.CmdFlags.Config
if strings.HasPrefix(configFile, "~") {
configFile = strings.TrimPrefix(configFile, "~")
configFile = filepath.Join(env.Home(), configFile)
}
if !filepath.IsAbs(configFile) {
2022-11-02 03:48:19 -07:00
configFile = filepath.Join(env.Pwd(), configFile)
}
2022-03-12 13:04:08 -08:00
env.CmdFlags.Config = filepath.Clean(configFile)
}
2022-11-09 11:27:54 -08:00
func (env *Shell) downloadConfig(location string) error {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), location)
ext := filepath.Ext(location)
configPath := filepath.Join(env.CachePath(), "config.omp"+ext)
2022-07-17 12:11:23 -07:00
cfg, err := env.HTTPRequest(location, nil, 5000)
if err != nil {
return err
}
out, err := os.Create(configPath)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, bytes.NewReader(cfg))
if err != nil {
return err
}
env.CmdFlags.Config = configPath
return nil
}
2023-01-16 11:58:43 -08:00
func (env *Shell) Trace(start time.Time, args ...string) {
log.Trace(start, args...)
}
2023-01-16 11:58:43 -08:00
func (env *Shell) Debug(message string) {
log.Debug(message)
}
2023-01-16 11:58:43 -08:00
func (env *Shell) Error(err error) {
log.Error(err)
}
2023-01-16 11:58:43 -08:00
func (env *Shell) debugF(fn func() string) {
log.DebugF(fn)
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Getenv(key string) string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), key)
val := os.Getenv(key)
2023-01-16 11:58:43 -08:00
env.Debug(val)
return val
2019-03-13 04:14:30 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Pwd() string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
defer env.Debug(env.cwd)
2020-10-12 00:04:37 -07:00
if env.cwd != "" {
return env.cwd
}
correctPath := func(pwd string) string {
if env.GOOS() != WINDOWS {
return pwd
2022-10-03 07:46:16 -07:00
}
// on Windows, and being case sensitive and not consistent and all, this gives silly issues
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper)
}
2022-03-12 13:04:08 -08:00
if env.CmdFlags != nil && env.CmdFlags.PWD != "" {
env.cwd = correctPath(env.CmdFlags.PWD)
2020-10-12 00:04:37 -07:00
return env.cwd
}
dir, err := os.Getwd()
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
return ""
}
2020-10-12 00:04:37 -07:00
env.cwd = correctPath(dir)
return env.cwd
}
2022-11-09 11:27:54 -08:00
func (env *Shell) HasFiles(pattern string) bool {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), pattern)
cwd := env.Pwd()
2022-11-02 07:04:20 -07:00
fileSystem := os.DirFS(cwd)
matches, err := fs.Glob(fileSystem, pattern)
2020-10-01 11:57:02 -07:00
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
2020-10-01 11:57:02 -07:00
return false
}
for _, match := range matches {
2022-11-02 07:04:20 -07:00
file, err := fs.Stat(fileSystem, match)
if err != nil || file.IsDir() {
continue
}
return true
}
return false
2020-10-01 11:57:02 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) HasFilesInDir(dir, pattern string) bool {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), pattern)
2022-11-02 07:04:20 -07:00
fileSystem := os.DirFS(dir)
matches, err := fs.Glob(fileSystem, pattern)
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
return false
}
hasFilesInDir := len(matches) > 0
2023-01-16 11:58:43 -08:00
env.debugF(func() string { return strconv.FormatBool(hasFilesInDir) })
return hasFilesInDir
}
2022-11-09 11:27:54 -08:00
func (env *Shell) HasFileInParentDirs(pattern string, depth uint) bool {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), pattern, fmt.Sprint(depth))
2022-02-13 23:41:33 -08:00
currentFolder := env.Pwd()
for c := 0; c < int(depth); c++ {
if env.HasFilesInDir(currentFolder, pattern) {
2023-01-16 11:58:43 -08:00
env.Debug("true")
2022-02-13 23:41:33 -08:00
return true
}
if dir := filepath.Dir(currentFolder); dir != currentFolder {
currentFolder = dir
} else {
2023-01-16 11:58:43 -08:00
env.Debug("false")
2022-02-13 23:41:33 -08:00
return false
}
}
2023-01-16 11:58:43 -08:00
env.Debug("false")
2022-02-13 23:41:33 -08:00
return false
}
2022-11-09 11:27:54 -08:00
func (env *Shell) HasFolder(folder string) bool {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), folder)
f, err := os.Stat(folder)
if err != nil {
2023-01-16 11:58:43 -08:00
env.Debug("false")
return false
}
2023-01-16 11:58:43 -08:00
env.debugF(func() string { return strconv.FormatBool(f.IsDir()) })
return f.IsDir()
2020-10-07 04:32:42 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) ResolveSymlink(path string) (string, error) {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), path)
link, err := filepath.EvalSymlinks(path)
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
return "", err
}
2023-01-16 11:58:43 -08:00
env.Debug(link)
return link, nil
}
2022-11-09 11:27:54 -08:00
func (env *Shell) FileContent(file string) string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), file)
if !filepath.IsAbs(file) {
file = filepath.Join(env.Pwd(), file)
}
2022-06-01 00:50:20 -07:00
content, err := os.ReadFile(file)
2020-10-07 04:32:42 -07:00
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
2020-10-07 04:32:42 -07:00
return ""
}
fileContent := string(content)
2023-01-16 11:58:43 -08:00
env.Debug(fileContent)
return fileContent
2020-10-07 04:32:42 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) LsDir(path string) []fs.DirEntry {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), path)
entries, err := os.ReadDir(path)
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
return nil
}
2023-01-16 11:58:43 -08:00
env.debugF(func() string {
var entriesStr string
for _, entry := range entries {
entriesStr += entry.Name() + "\n"
}
return entriesStr
})
return entries
}
2022-11-09 11:27:54 -08:00
func (env *Shell) PathSeparator() string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2019-03-13 04:14:30 -07:00
return string(os.PathSeparator)
}
2022-11-09 11:27:54 -08:00
func (env *Shell) User() string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
user := os.Getenv("USER")
if user == "" {
user = os.Getenv("USERNAME")
}
2023-01-16 11:58:43 -08:00
env.Debug(user)
return user
2019-03-13 04:14:30 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Host() (string, error) {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2019-03-13 04:14:30 -07:00
hostName, err := os.Hostname()
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
2019-03-13 04:14:30 -07:00
return "", err
}
hostName = cleanHostName(hostName)
2023-01-16 11:58:43 -08:00
env.Debug(hostName)
return hostName, nil
2019-03-13 04:14:30 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) GOOS() string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2019-03-13 04:14:30 -07:00
return runtime.GOOS
}
2022-11-09 11:27:54 -08:00
func (env *Shell) RunCommand(command string, args ...string) (string, error) {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), append([]string{command}, args...)...)
if cacheCommand, ok := env.cmdCache.get(command); ok {
command = cacheCommand
}
output, err := cmd.Run(command, args...)
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
}
2023-01-16 11:58:43 -08:00
env.Debug(output)
return output, err
2019-03-13 04:14:30 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) RunShellCommand(shell, command string) string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
if out, err := env.RunCommand(shell, "-c", command); err == nil {
return out
}
return ""
2019-03-13 04:14:30 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) CommandPath(command string) string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), command)
if path, ok := env.cmdCache.get(command); ok {
2023-01-16 11:58:43 -08:00
env.Debug(path)
return path
}
path, err := exec.LookPath(command)
if err == nil {
env.cmdCache.set(command, path)
2023-01-16 11:58:43 -08:00
env.Debug(path)
return path
}
2023-01-16 11:58:43 -08:00
env.Error(err)
return ""
}
2022-11-09 11:27:54 -08:00
func (env *Shell) HasCommand(command string) bool {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), command)
if path := env.CommandPath(command); path != "" {
return true
}
return false
2019-03-13 04:14:30 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) ErrorCode() int {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2022-03-12 13:04:08 -08:00
return env.CmdFlags.ErrorCode
2019-03-13 04:14:30 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) ExecutionTime() float64 {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2022-03-12 13:04:08 -08:00
if env.CmdFlags.ExecutionTime < 0 {
return 0
}
2022-03-12 13:04:08 -08:00
return env.CmdFlags.ExecutionTime
2020-12-06 13:03:40 -08:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Flags() *Flags {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2022-03-12 13:04:08 -08:00
return env.CmdFlags
2019-03-13 04:14:30 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Shell() string {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2022-03-12 13:04:08 -08:00
if env.CmdFlags.Shell != "" {
return env.CmdFlags.Shell
2020-12-27 05:59:40 -08:00
}
2020-09-15 04:44:53 -07:00
pid := os.Getppid()
2020-10-23 07:36:40 -07:00
p, _ := process.NewProcess(int32(pid))
name, err := p.Name()
2020-09-24 10:11:56 -07:00
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
2022-08-02 22:25:59 -07:00
return UNKNOWN
2020-09-24 10:11:56 -07:00
}
2023-01-16 11:58:43 -08:00
env.Debug("process name: " + name)
// this is used for when scoop creates a shim, see
2022-12-28 08:30:48 -08:00
// https://github.com/jandedobbeleer/oh-my-posh/issues/2806
executable, _ := os.Executable()
if name == "cmd.exe" || name == executable {
p, _ = p.Parent()
name, err = p.Name()
2023-01-16 11:58:43 -08:00
env.Debug("parent process name: " + name)
}
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
2022-08-02 22:25:59 -07:00
return UNKNOWN
}
// Cache the shell value to speed things up.
env.CmdFlags.Shell = strings.Trim(strings.TrimSuffix(name, ".exe"), " ")
2022-03-12 13:04:08 -08:00
return env.CmdFlags.Shell
2020-09-15 04:44:53 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) unWrapError(err error) error {
2022-08-03 22:06:55 -07:00
cause := err
for {
type nested interface{ Unwrap() error }
unwrap, ok := cause.(nested)
if !ok {
break
}
cause = unwrap.Unwrap()
}
return cause
}
2022-11-09 11:27:54 -08:00
func (env *Shell) HTTPRequest(targetURL string, body io.Reader, timeout int, requestModifiers ...HTTPRequestModifier) ([]byte, error) {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), targetURL)
ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
defer cncl()
2022-07-17 12:11:23 -07:00
request, err := http.NewRequestWithContext(ctx, http.MethodGet, targetURL, body)
if err != nil {
return nil, err
}
2021-12-14 23:49:32 -08:00
for _, modifier := range requestModifiers {
modifier(request)
}
2022-07-17 12:11:23 -07:00
if env.CmdFlags.Debug {
dump, _ := httputil.DumpRequestOut(request, true)
2023-01-16 11:58:43 -08:00
env.Debug(string(dump))
2022-07-17 12:11:23 -07:00
}
response, err := client.Do(request)
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
2022-08-03 22:06:55 -07:00
return nil, env.unWrapError(err)
}
// anything inside the range [200, 299] is considered a success
if response.StatusCode < 200 || response.StatusCode >= 300 {
message := "HTTP status code " + strconv.Itoa(response.StatusCode)
err := errors.New(message)
2023-01-16 11:58:43 -08:00
env.Error(err)
return nil, err
}
defer response.Body.Close()
2022-07-17 12:11:23 -07:00
responseBody, err := io.ReadAll(response.Body)
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
return nil, err
}
2023-01-16 11:58:43 -08:00
env.Debug(string(responseBody))
2022-07-17 12:11:23 -07:00
return responseBody, nil
}
2022-11-09 11:27:54 -08:00
func (env *Shell) HasParentFilePath(path string) (*FileInfo, error) {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now(), path)
currentFolder := env.Pwd()
for {
fileSystem := os.DirFS(currentFolder)
info, err := fs.Stat(fileSystem, path)
if err == nil {
return &FileInfo{
ParentFolder: currentFolder,
Path: filepath.Join(currentFolder, path),
IsDir: info.IsDir(),
}, nil
}
if !os.IsNotExist(err) {
return nil, err
}
if dir := filepath.Dir(currentFolder); dir != currentFolder {
currentFolder = dir
continue
}
2023-01-16 11:58:43 -08:00
env.Error(err)
return nil, errors.New("no match at root level")
}
}
2022-11-09 11:27:54 -08:00
func (env *Shell) StackCount() int {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
2022-03-12 13:04:08 -08:00
if env.CmdFlags.StackCount < 0 {
return 0
}
2022-03-12 13:04:08 -08:00
return env.CmdFlags.StackCount
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Cache() Cache {
2021-09-21 11:22:59 -07:00
return env.fileCache
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Close() {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
templateCache, err := json.Marshal(env.TemplateCache())
if err == nil {
env.fileCache.Set(TEMPLATECACHE, string(templateCache), 1440)
}
env.fileCache.Close()
}
2022-11-09 11:27:54 -08:00
func (env *Shell) LoadTemplateCache() {
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
val, OK := env.fileCache.Get(TEMPLATECACHE)
if !OK {
return
}
var templateCache TemplateCache
err := json.Unmarshal([]byte(val), &templateCache)
if err != nil {
2023-01-16 11:58:43 -08:00
env.Error(err)
return
}
env.tmplCache = &templateCache
}
2022-11-09 11:27:54 -08:00
func (env *Shell) Logs() string {
return log.String()
2021-09-21 11:22:59 -07:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) TemplateCache() *TemplateCache {
env.Lock()
2023-01-16 11:58:43 -08:00
defer env.Trace(time.Now())
defer env.Unlock()
2022-01-18 00:48:47 -08:00
if env.tmplCache != nil {
return env.tmplCache
}
tmplCache := &TemplateCache{
Root: env.Root(),
Shell: env.Shell(),
ShellVersion: env.CmdFlags.ShellVersion,
Code: env.ErrorCode(),
WSL: env.IsWsl(),
Segments: make(map[string]interface{}),
PromptCount: env.CmdFlags.PromptCount,
2022-01-18 00:48:47 -08:00
}
tmplCache.Env = make(map[string]string)
2023-02-19 11:21:33 -08:00
tmplCache.Var = make(map[string]interface{})
if env.Var != nil {
tmplCache.Var = env.Var
}
2022-01-18 00:48:47 -08:00
const separator = "="
values := os.Environ()
for value := range values {
key, val, valid := strings.Cut(values[value], separator)
if !valid {
2022-01-18 00:48:47 -08:00
continue
}
tmplCache.Env[key] = val
2022-01-18 00:48:47 -08:00
}
2022-08-31 02:55:23 -07:00
pwd := env.Pwd()
tmplCache.PWD = ReplaceHomeDirPrefixWithTilde(env, pwd)
2022-08-31 02:55:23 -07:00
tmplCache.Folder = Base(env, pwd)
2022-10-03 07:46:16 -07:00
if env.GOOS() == WINDOWS && strings.HasSuffix(tmplCache.Folder, ":") {
tmplCache.Folder += `\`
}
tmplCache.UserName = env.User()
if host, err := env.Host(); err == nil {
tmplCache.HostName = host
2022-01-18 00:48:47 -08:00
}
goos := env.GOOS()
tmplCache.OS = goos
2022-08-02 22:25:59 -07:00
if goos == LINUX {
tmplCache.OS = env.Platform()
}
env.tmplCache = tmplCache
return tmplCache
2022-01-18 00:48:47 -08:00
}
2022-11-09 11:27:54 -08:00
func (env *Shell) DirMatchesOneOf(dir string, regexes []string) (match bool) {
// sometimes the function panics inside golang, we want to silence that error
// and assume that there's no match. Not perfect, but better than crashing
// for the time being until we figure out what the actual root cause is
defer func() {
if err := recover(); err != nil {
2023-01-16 11:58:43 -08:00
env.Error(errors.New("panic"))
match = false
}
}()
match = dirMatchesOneOf(dir, env.Home(), env.GOOS(), regexes)
return
}
func dirMatchesOneOf(dir, home, goos string, regexes []string) bool {
if len(regexes) == 0 {
return false
}
2022-10-03 07:46:16 -07:00
if goos == WINDOWS {
dir = strings.ReplaceAll(dir, "\\", "/")
home = strings.ReplaceAll(home, "\\", "/")
}
for _, element := range regexes {
normalizedElement := strings.ReplaceAll(element, "\\\\", "/")
if strings.HasPrefix(normalizedElement, "~") {
2022-10-03 07:46:16 -07:00
normalizedElement = strings.Replace(normalizedElement, "~", home, 1)
}
pattern := fmt.Sprintf("^%s$", normalizedElement)
2022-08-02 22:25:59 -07:00
if goos == WINDOWS || goos == DARWIN {
pattern = "(?i)" + pattern
}
2022-10-03 07:46:16 -07:00
matched := regex.MatchString(pattern, dir)
if matched {
return true
}
}
return false
}
func (env *Shell) SetPromptCount() {
countStr := os.Getenv("POSH_PROMPT_COUNT")
if len(countStr) > 0 {
// this counter is incremented by the shell
count, err := strconv.Atoi(countStr)
if err == nil {
env.CmdFlags.PromptCount = count
return
}
}
var count int
if val, found := env.Cache().Get(PROMPTCOUNTCACHE); found {
count, _ = strconv.Atoi(val)
}
// only write to cache if we're the primary prompt
if env.CmdFlags.Primary {
count++
env.Cache().Set(PROMPTCOUNTCACHE, strconv.Itoa(count), 1440)
}
env.CmdFlags.PromptCount = count
}
func (env *Shell) CursorPosition() (row, col int) {
if number, err := strconv.Atoi(env.Getenv("POSH_CURSOR_LINE")); err == nil {
row = number
}
if number, err := strconv.Atoi(env.Getenv("POSH_CURSOR_COLUMN")); err != nil {
col = number
}
return
}
func (env *Shell) SystemInfo() (*SystemInfo, error) {
s := &SystemInfo{}
mem, err := env.Memory()
if err != nil {
return nil, err
}
s.Memory = *mem
loadStat, err := load.Avg()
if err == nil {
s.Load1 = loadStat.Load1
s.Load5 = loadStat.Load5
s.Load15 = loadStat.Load15
}
processorTimes, err := cpu.Percent(0, false)
if err == nil && len(processorTimes) > 0 {
s.Times = processorTimes[0]
}
processors, err := cpu.Info()
if err == nil {
s.CPU = processors
}
diskIO, err := disk.IOCounters()
if err == nil {
s.Disks = diskIO
}
return s, nil
}
2022-10-03 07:46:16 -07:00
func IsPathSeparator(env Environment, c uint8) bool {
if c == '/' {
return true
}
if env.GOOS() == WINDOWS && c == '\\' {
return true
}
return false
}
// Base returns the last element of path.
// Trailing path separators are removed before extracting the last element.
// If the path consists entirely of separators, Base returns a single separator.
func Base(env Environment, path string) string {
volumeName := filepath.VolumeName(path)
// Strip trailing slashes.
2022-10-03 07:46:16 -07:00
for len(path) > 0 && IsPathSeparator(env, path[len(path)-1]) {
path = path[0 : len(path)-1]
}
2022-10-03 07:46:16 -07:00
if len(path) == 0 {
return env.PathSeparator()
}
if volumeName == path {
return path
}
// Throw away volume name
path = path[len(filepath.VolumeName(path)):]
// Find the last element
i := len(path) - 1
2022-10-03 07:46:16 -07:00
for i >= 0 && !IsPathSeparator(env, path[i]) {
i--
}
if i >= 0 {
path = path[i+1:]
}
// If empty now, it had only slashes.
2022-10-03 07:46:16 -07:00
if len(path) == 0 {
2022-02-19 10:04:06 -08:00
return env.PathSeparator()
}
return path
}
func ReplaceHomeDirPrefixWithTilde(env Environment, path string) string {
2022-10-03 07:46:16 -07:00
home := env.Home()
// match Home directory exactly
if !strings.HasPrefix(path, home) {
return path
}
rem := path[len(home):]
if len(rem) == 0 || IsPathSeparator(env, rem[0]) {
return "~" + rem
}
return path
}
2019-03-13 04:14:30 -07:00
func cleanHostName(hostName string) string {
garbage := []string{
".lan",
".local",
".localdomain",
2019-03-13 04:14:30 -07:00
}
for _, g := range garbage {
if strings.HasSuffix(hostName, g) {
hostName = strings.Replace(hostName, g, "", 1)
}
2019-03-13 04:14:30 -07:00
}
return hostName
}
func returnOrBuildCachePath(path string) string {
// validate root path
if _, err := os.Stat(path); err != nil {
return ""
}
// validate oh-my-posh folder, if non existent, create it
2022-02-28 03:55:16 -08:00
cachePath := filepath.Join(path, "oh-my-posh")
if _, err := os.Stat(cachePath); err == nil {
return cachePath
}
if err := os.Mkdir(cachePath, 0755); err != nil {
return ""
}
return cachePath
}