diff --git a/src/font/cli.go b/src/font/cli.go index 64a995b945c4..e9a32eefccb7 100644 --- a/src/font/cli.go +++ b/src/font/cli.go @@ -72,14 +72,13 @@ const ( ) type main struct { - err error - list *list.Model - font string - zipFolder string - families []string - spinner spinner.Model - state state - system bool + err error + list *list.Model + Asset + families []string + spinner spinner.Model + state state + system bool } func (m *main) buildFontList(nerdFonts []*Asset) { @@ -122,7 +121,7 @@ func downloadFontZip(location string) { } func installLocalFontZIP(m *main) { - data, err := os.ReadFile(m.font) + data, err := os.ReadFile(m.URL) if err != nil { program.Send(errMsg(err)) return @@ -143,28 +142,48 @@ func installFontZIP(zipFile []byte, m *main) { func (m *main) Init() tea.Cmd { isLocalZipFile := func() bool { - return !strings.HasPrefix(m.font, "https") && strings.HasSuffix(m.font, ".zip") + return !strings.HasPrefix(m.URL, "https") && strings.HasSuffix(m.URL, ".zip") } - if len(m.font) != 0 && !isLocalZipFile() { - m.state = downloadFont + resolveFontZipURL := func() error { + if strings.HasPrefix(m.URL, "https") { + return nil + } - if !strings.HasPrefix(m.font, "https") { - if m.font == CascadiaCodeMS { - cascadia, err := CascadiaCode() - if err != nil { - m.err = err - return tea.Quit - } + fonts, err := Fonts() + if err != nil { + return err + } - m.font = cascadia.URL - } else { - m.font = fmt.Sprintf("https://github.com/ryanoasis/nerd-fonts/releases/latest/download/%s.zip", m.font) + var fontAsset *Asset + for _, font := range fonts { + if m.URL != font.Name { + continue } + + fontAsset = font + break + } + + if fontAsset == nil { + return fmt.Errorf("no matching font found") + } + + m.Asset = *fontAsset + + return nil + } + + if len(m.URL) != 0 && !isLocalZipFile() { + m.state = downloadFont + + if err := resolveFontZipURL(); err != nil { + m.err = err + return tea.Quit } defer func() { - go downloadFontZip(m.font) + go downloadFontZip(m.URL) }() m.spinner.Spinner = spinner.Globe @@ -214,20 +233,25 @@ func (m *main) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit case "enter": - if len(m.font) != 0 || m.list == nil || m.list.SelectedItem() == nil { + if len(m.URL) != 0 || m.list == nil || m.list.SelectedItem() == nil { return m, nil } + var font *Asset var ok bool + if font, ok = m.list.SelectedItem().(*Asset); !ok { m.err = fmt.Errorf("no font selected") return m, tea.Quit } + m.state = downloadFont - m.font = font.Name + m.Asset = *font + defer func() { go downloadFontZip(font.URL) }() + m.spinner.Spinner = spinner.Globe return m, m.spinner.Tick @@ -295,11 +319,11 @@ func (m *main) View() string { case selectFont: return fmt.Sprintf("\n%s%s", m.list.View(), terminal.StopProgress()) case downloadFont: - return textStyle.Render(fmt.Sprintf("%s Downloading %s%s", m.spinner.View(), m.font, terminal.StartProgress())) + return textStyle.Render(fmt.Sprintf("%s Downloading %s%s", m.spinner.View(), m.Name, terminal.StartProgress())) case unzipFont: - return textStyle.Render(fmt.Sprintf("%s Extracting %s", m.spinner.View(), m.font)) + return textStyle.Render(fmt.Sprintf("%s Extracting %s", m.spinner.View(), m.Name)) case installFont: - return textStyle.Render(fmt.Sprintf("%s Installing %s", m.spinner.View(), m.font)) + return textStyle.Render(fmt.Sprintf("%s Installing %s", m.spinner.View(), m.Name)) case quit: return textStyle.Render(fmt.Sprintf("No need to install a new font? That's cool.%s", terminal.StopProgress())) case done: @@ -309,7 +333,7 @@ func (m *main) View() string { var builder strings.Builder - builder.WriteString(fmt.Sprintf("Successfully installed %s 🚀\n\n%s", m.font, terminal.StopProgress())) + builder.WriteString(fmt.Sprintf("Successfully installed %s 🚀\n\n%s", m.Name, terminal.StopProgress())) builder.WriteString("The following font families are now available for configuration:\n\n") for i, family := range m.families { @@ -328,9 +352,12 @@ func (m *main) View() string { func Run(font string, ch cache_.Cache, root bool, zipFolder string) { main := &main{ - font: font, - system: root, - zipFolder: zipFolder, + system: root, + Asset: Asset{ + Name: font, + URL: font, + Folder: zipFolder, + }, } cache = ch diff --git a/src/font/download.go b/src/font/download.go index 5c03fabccee2..8d3ebfbdc9bc 100644 --- a/src/font/download.go +++ b/src/font/download.go @@ -10,25 +10,55 @@ import ( "io" httplib "net/http" "net/url" + "os" + "path" + "path/filepath" + cache_ "github.com/jandedobbeleer/oh-my-posh/src/cache" "github.com/jandedobbeleer/oh-my-posh/src/runtime/http" ) -func Download(fontPath string) ([]byte, error) { - u, err := url.Parse(fontPath) +func Download(fontURL string) ([]byte, error) { + if zipPath, OK := cache.Get(fontURL); OK { + if b, err := os.ReadFile(zipPath); err == nil { + return b, nil + } + } + + // validate if we have a local file + u, err := url.Parse(fontURL) if err != nil || u.Scheme != "https" { return nil, errors.New("font path must be a valid URL") } var b []byte - if b, err = getRemoteFile(fontPath); err != nil { + if b, err = getRemoteFile(fontURL); err != nil { return nil, err } if !isZipFile(b) { - return nil, fmt.Errorf("%s is not a valid zip file", fontPath) + return nil, fmt.Errorf("%s is not a valid zip file", fontURL) + } + + fileName := path.Base(fontURL) + + zipPath := filepath.Join(os.TempDir(), fileName) + tempFile, err := os.Create(zipPath) + defer func() { + _ = tempFile.Close() + }() + + if err != nil { + return b, nil } + _, err = tempFile.Write(b) + if err != nil { + return b, nil + } + + cache.Set(fontURL, zipPath, cache_.ONEDAY) + return b, nil } @@ -42,10 +72,12 @@ func getRemoteFile(location string) (data []byte, err error) { if err != nil { return nil, err } + resp, err := http.HTTPClient.Do(req) if err != nil { return } + defer resp.Body.Close() if resp.StatusCode != httplib.StatusOK { diff --git a/src/font/fonts.go b/src/font/fonts.go index 637ce3a8dff5..0cf08ef2871b 100644 --- a/src/font/fonts.go +++ b/src/font/fonts.go @@ -23,9 +23,10 @@ type release struct { } type Asset struct { - Name string `json:"name"` - URL string `json:"browser_download_url"` - State string `json:"state"` + Name string `json:"name"` + URL string `json:"browser_download_url"` + State string `json:"state"` + Folder string `json:"folder"` } func (a Asset) FilterValue() string { return a.Name } @@ -42,7 +43,7 @@ func Fonts() ([]*Asset, error) { cascadiaCode, err := CascadiaCode() if err == nil { - assets = append(assets, cascadiaCode) + assets = append(assets, cascadiaCode...) } sort.Slice(assets, func(i, j int) bool { return assets[i].Name < assets[j].Name }) @@ -84,16 +85,39 @@ func setCachedFontData(assets []*Asset) { cache.Set(cache_.FONTLISTCACHE, string(data), cache_.ONEDAY) } -func CascadiaCode() (*Asset, error) { +func CascadiaCode() ([]*Asset, error) { assets, err := fetchFontAssets("microsoft/cascadia-code") if err != nil || len(assets) != 1 { return nil, errors.New("no assets found") } - // patch the name - assets[0].Name = CascadiaCodeMS - - return assets[0], nil + return []*Asset{ + { + Name: fmt.Sprintf("%s - TTF", CascadiaCodeMS), + URL: assets[0].URL, + Folder: "ttf/", + }, + { + Name: fmt.Sprintf("%s - TTF Static", CascadiaCodeMS), + URL: assets[0].URL, + Folder: "ttf/static/", + }, + { + Name: fmt.Sprintf("%s - OTF Static", CascadiaCodeMS), + URL: assets[0].URL, + Folder: "otf/static/", + }, + { + Name: fmt.Sprintf("%s - WOFF2", CascadiaCodeMS), + URL: assets[0].URL, + Folder: "woff2/static/", + }, + { + Name: fmt.Sprintf("%s - WOFF2 Static", CascadiaCodeMS), + URL: assets[0].URL, + Folder: "woff2/static/", + }, + }, nil } func fetchFontAssets(repo string) ([]*Asset, error) { diff --git a/src/font/install.go b/src/font/install.go index d23c657c65f5..a7dbee50c3fb 100644 --- a/src/font/install.go +++ b/src/font/install.go @@ -45,7 +45,7 @@ func InstallZIP(data []byte, m *main) ([]string, error) { } fontFileName := path.Base(file.Name) - fontRelativeFileName := strings.TrimPrefix(file.Name, m.zipFolder) + fontRelativeFileName := strings.TrimPrefix(file.Name, m.Folder) // do not install fonts that are not in the specified installation folder if fontFileName != fontRelativeFileName { diff --git a/src/image/image.go b/src/image/image.go index b97c4cb823bf..9580d2066223 100644 --- a/src/image/image.go +++ b/src/image/image.go @@ -36,7 +36,7 @@ import ( "unicode/utf8" "github.com/jandedobbeleer/oh-my-posh/src/cache" - fontCLI "github.com/jandedobbeleer/oh-my-posh/src/font" + font_ "github.com/jandedobbeleer/oh-my-posh/src/font" "github.com/jandedobbeleer/oh-my-posh/src/regex" "github.com/jandedobbeleer/oh-my-posh/src/runtime" "github.com/jandedobbeleer/oh-my-posh/src/terminal" @@ -229,7 +229,7 @@ func (ir *Renderer) loadFonts() error { url := "https://github.com/ryanoasis/nerd-fonts/releases/download/v3.2.1/Hack.zip" var err error - data, err = fontCLI.Download(url) + data, err = font_.Download(url) if err != nil { return err } diff --git a/website/docs/installation/fonts.mdx b/website/docs/installation/fonts.mdx index ffe8e9ba95f4..a73f261188a1 100644 --- a/website/docs/installation/fonts.mdx +++ b/website/docs/installation/fonts.mdx @@ -47,12 +47,6 @@ This will present a list of Nerd Font libraries, from which you can select `Mes oh-my-posh font install meslo ``` -If you have a font that has specific flavors of a font inside sub folders, you can specify the sub folder name: - -```bash -oh-my-posh font install --zip-folder ttf/static "CascadiaCode (MS)" -``` -