feat(font): allow installing from a specific zip file folder
Some checks are pending
Code QL / code-ql (push) Waiting to run
Azure Static Web Apps CI/CD / Build and Deploy (push) Waiting to run
Release / changelog (push) Waiting to run
Release / artifacts (push) Blocked by required conditions
Release / msi (arm64) (push) Blocked by required conditions
Release / msi (x64) (push) Blocked by required conditions
Release / msi (x86) (push) Blocked by required conditions
Release / release (push) Blocked by required conditions

This commit is contained in:
Jan De Dobbeleer 2024-12-17 12:03:16 +01:00 committed by Jan De Dobbeleer
parent b4b2fd02d5
commit a7ad857a2d
4 changed files with 73 additions and 54 deletions

View file

@ -11,7 +11,7 @@ import (
) )
var ( var (
ttf bool zipFolder string
fontCmd = &cobra.Command{ fontCmd = &cobra.Command{
Use: "font [install|configure]", Use: "font [install|configure]",
@ -47,7 +47,7 @@ This command is used to install fonts and configure the font in your terminal.
terminal.Init(env.Shell()) terminal.Init(env.Shell())
font.Run(fontName, env.Cache(), env.Root(), ttf) font.Run(fontName, env.Cache(), env.Root(), zipFolder)
return return
case "configure": case "configure":
@ -60,6 +60,6 @@ This command is used to install fonts and configure the font in your terminal.
) )
func init() { func init() {
fontCmd.Flags().BoolVar(&ttf, "ttf", false, "fetch the TTF version of the font") fontCmd.Flags().StringVar(&zipFolder, "zip-folder", "", "the folder inside the zip file to install fonts from")
RootCmd.AddCommand(fontCmd) RootCmd.AddCommand(fontCmd)
} }

View file

@ -72,14 +72,14 @@ const (
) )
type main struct { type main struct {
err error err error
list *list.Model list *list.Model
font string font string
families []string zipFolder string
spinner spinner.Model families []string
state state spinner spinner.Model
system bool state state
ttf bool system bool
} }
func (m *main) buildFontList(nerdFonts []*Asset) { func (m *main) buildFontList(nerdFonts []*Asset) {
@ -121,18 +121,18 @@ func downloadFontZip(location string) {
program.Send(zipMsg(zipFile)) program.Send(zipMsg(zipFile))
} }
func installLocalFontZIP(zipFile string, user, ttf bool) { func installLocalFontZIP(m *main) {
data, err := os.ReadFile(zipFile) data, err := os.ReadFile(m.font)
if err != nil { if err != nil {
program.Send(errMsg(err)) program.Send(errMsg(err))
return return
} }
installFontZIP(data, user, ttf) installFontZIP(data, m)
} }
func installFontZIP(zipFile []byte, user, ttf bool) { func installFontZIP(zipFile []byte, m *main) {
families, err := InstallZIP(zipFile, user, ttf) families, err := InstallZIP(zipFile, m)
if err != nil { if err != nil {
program.Send(errMsg(err)) program.Send(errMsg(err))
return return
@ -148,21 +148,30 @@ func (m *main) Init() tea.Cmd {
if len(m.font) != 0 && !isLocalZipFile() { if len(m.font) != 0 && !isLocalZipFile() {
m.state = downloadFont m.state = downloadFont
if !strings.HasPrefix(m.font, "https") { if !strings.HasPrefix(m.font, "https") {
m.font = fmt.Sprintf("https://github.com/ryanoasis/nerd-fonts/releases/latest/download/%s.zip", m.font) if strings.HasPrefix(m.font, "CascadiaCode-") {
version := strings.TrimPrefix(m.font, "CascadiaCode-")
m.font = fmt.Sprintf("https://github.com/microsoft/cascadia-code/releases/download/v%s/%s.zip", version, m.font)
} else {
m.font = fmt.Sprintf("https://github.com/ryanoasis/nerd-fonts/releases/latest/download/%s.zip", m.font)
}
} }
defer func() { defer func() {
go downloadFontZip(m.font) go downloadFontZip(m.font)
}() }()
m.spinner.Spinner = spinner.Globe m.spinner.Spinner = spinner.Globe
return m.spinner.Tick return m.spinner.Tick
} }
defer func() { defer func() {
if isLocalZipFile() { if isLocalZipFile() {
go installLocalFontZIP(m.font, m.system, m.ttf) go installLocalFontZIP(m)
return return
} }
go getFontsList() go getFontsList()
}() }()
@ -171,6 +180,7 @@ func (m *main) Init() tea.Cmd {
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170")) s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170"))
m.spinner = s m.spinner = s
m.state = getFonts m.state = getFonts
if isLocalZipFile() { if isLocalZipFile() {
m.state = unzipFont m.state = unzipFont
} }
@ -240,7 +250,7 @@ func (m *main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case zipMsg: case zipMsg:
m.state = installFont m.state = installFont
defer func() { defer func() {
go installFontZIP(msg, m.system, m.ttf) go installFontZIP(msg, m)
}() }()
m.spinner.Spinner = spinner.Dot m.spinner.Spinner = spinner.Dot
return m, m.spinner.Tick return m, m.spinner.Tick
@ -288,6 +298,10 @@ func (m *main) View() string {
case quit: case quit:
return textStyle.Render(fmt.Sprintf("No need to install a new font? That's cool.%s", terminal.StopProgress())) return textStyle.Render(fmt.Sprintf("No need to install a new font? That's cool.%s", terminal.StopProgress()))
case done: case done:
if len(m.families) == 0 {
return textStyle.Render(fmt.Sprintf("No matching font families were installed. Try setting --zip-folder to the correct folder when using Cascadia Code or a custom font zip file %s", terminal.StopProgress())) //nolint: lll
}
var builder strings.Builder 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.font, terminal.StopProgress()))
@ -307,11 +321,11 @@ func (m *main) View() string {
return "" return ""
} }
func Run(font string, ch cache_.Cache, root, ttf bool) { func Run(font string, ch cache_.Cache, root bool, zipFolder string) {
main := &main{ main := &main{
font: font, font: font,
system: root, system: root,
ttf: ttf, zipFolder: zipFolder,
} }
cache = ch cache = ch

View file

@ -25,13 +25,7 @@ func contains[S ~[]E, E comparable](s S, e E) bool {
return false return false
} }
func InstallZIP(data []byte, user, ttf bool) ([]string, error) { func InstallZIP(data []byte, m *main) ([]string, error) {
// prefer OTF over TTF; otherwise prefer the first font we find
extension := ".otf"
if ttf {
extension = ".ttf"
}
var families []string var families []string
bytesReader := bytes.NewReader(data) bytesReader := bytes.NewReader(data)
@ -42,46 +36,56 @@ func InstallZIP(data []byte, user, ttf bool) ([]string, error) {
fonts := make(map[string]*Font) fonts := make(map[string]*Font)
for _, zf := range zipReader.File { root := len(m.zipFolder) == 0
for _, file := range zipReader.File {
// prevent zipslip attacks // prevent zipslip attacks
// https://security.snyk.io/research/zip-slip-vulnerability // https://security.snyk.io/research/zip-slip-vulnerability
if strings.Contains(zf.Name, "..") { // and only process files which are in the specified folder
if strings.Contains(file.Name, "..") || !strings.HasPrefix(file.Name, m.zipFolder) {
continue continue
} }
rc, err := zf.Open() fontFileName := path.Base(file.Name)
if err != nil {
return families, err // do not install fonts that are not in the root folder when specified as such
if root && fontFileName != file.Name {
continue
} }
defer rc.Close() fontReader, err := file.Open()
data, err := io.ReadAll(rc)
if err != nil {
return families, err
}
fontData, err := newFont(path.Base(zf.Name), data)
if err != nil { if err != nil {
continue continue
} }
if _, found := fonts[fontData.Name]; !found { defer fontReader.Close()
fonts[fontData.Name] = fontData
fontBytes, err := io.ReadAll(fontReader)
if err != nil {
continue continue
} }
// respect the user's preference for TTF or OTF font, err := newFont(fontFileName, fontBytes)
first := strings.ToLower(path.Ext(fonts[fontData.Name].FileName)) if err != nil {
second := strings.ToLower(path.Ext(fontData.FileName)) continue
if first != second && second == extension { }
fonts[fontData.Name] = fontData
if _, found := fonts[font.Name]; !found {
fonts[font.Name] = font
continue
}
// prefer .ttf files over other file types when we have a duplicate
first := strings.ToLower(path.Ext(fonts[font.Name].FileName))
second := strings.ToLower(path.Ext(font.FileName))
if first != second && second == ".ttf" {
fonts[font.Name] = font
} }
} }
for _, font := range fonts { for _, font := range fonts {
if err = install(font, user); err != nil { if err = install(font, m.system); err != nil {
return families, err continue
} }
if found := contains(families, font.Family); !found { if found := contains(families, font.Family); !found {

View file

@ -34,6 +34,7 @@ Oh My Posh has a CLI to help you select and install a [Nerd Font][nerdfonts]:
:::info :::info
When running as root/administrator, the fonts will be installed system-wide. When running as root/administrator, the fonts will be installed system-wide.
When running as a regular user, the fonts will be installed in the user's font directory. When running as a regular user, the fonts will be installed in the user's font directory.
By default, Oh My Posh installs the `.ttf` version of the font in case multiple versions are available.
::: :::
```bash ```bash
@ -46,10 +47,10 @@ This will present a list of Nerd Font libraries, from which you can select `Mes
oh-my-posh font install meslo oh-my-posh font install meslo
``` ```
By default, Oh My Posh installs the `.otf` version of the font. If you prefer the `.ttf` version, you can specify it with the `--ttf` flag: If you have a font that has specific flavors of a font inside sub folders, you can specify the sub folder name:
```bash ```bash
oh-my-posh font install meslo --ttf oh-my-posh font install --zip-folder ttf/static CascadiaCode-2407.24
``` ```
</TabItem> </TabItem>