Skip to content

Commit

Permalink
feat(font): allow installing from a specific zip file folder
Browse files Browse the repository at this point in the history
  • Loading branch information
JanDeDobbeleer committed Dec 17, 2024
1 parent b4b2fd0 commit a7ad857
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 49 deletions.
6 changes: 3 additions & 3 deletions src/cli/font.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

var (
ttf bool
zipFolder string

fontCmd = &cobra.Command{
Use: "font [install|configure]",
Expand Down Expand Up @@ -47,7 +47,7 @@ This command is used to install fonts and configure the font in your terminal.

terminal.Init(env.Shell())

font.Run(fontName, env.Cache(), env.Root(), ttf)
font.Run(fontName, env.Cache(), env.Root(), zipFolder)

return
case "configure":
Expand All @@ -60,6 +60,6 @@ This command is used to install fonts and configure the font in your terminal.
)

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)
}
54 changes: 34 additions & 20 deletions src/font/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ const (
)

type main struct {
err error
list *list.Model
font string
families []string
spinner spinner.Model
state state
system bool
ttf bool
err error
list *list.Model
font string
zipFolder string
families []string
spinner spinner.Model
state state
system bool
}

func (m *main) buildFontList(nerdFonts []*Asset) {
Expand Down Expand Up @@ -121,18 +121,18 @@ func downloadFontZip(location string) {
program.Send(zipMsg(zipFile))
}

func installLocalFontZIP(zipFile string, user, ttf bool) {
data, err := os.ReadFile(zipFile)
func installLocalFontZIP(m *main) {
data, err := os.ReadFile(m.font)
if err != nil {
program.Send(errMsg(err))
return
}

installFontZIP(data, user, ttf)
installFontZIP(data, m)
}

func installFontZIP(zipFile []byte, user, ttf bool) {
families, err := InstallZIP(zipFile, user, ttf)
func installFontZIP(zipFile []byte, m *main) {
families, err := InstallZIP(zipFile, m)
if err != nil {
program.Send(errMsg(err))
return
Expand All @@ -148,21 +148,30 @@ func (m *main) Init() tea.Cmd {

if len(m.font) != 0 && !isLocalZipFile() {
m.state = downloadFont

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() {
go downloadFontZip(m.font)
}()

m.spinner.Spinner = spinner.Globe
return m.spinner.Tick
}

defer func() {
if isLocalZipFile() {
go installLocalFontZIP(m.font, m.system, m.ttf)
go installLocalFontZIP(m)
return
}

go getFontsList()
}()

Expand All @@ -171,6 +180,7 @@ func (m *main) Init() tea.Cmd {
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("170"))
m.spinner = s
m.state = getFonts

if isLocalZipFile() {
m.state = unzipFont
}
Expand Down Expand Up @@ -240,7 +250,7 @@ func (m *main) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case zipMsg:
m.state = installFont
defer func() {
go installFontZIP(msg, m.system, m.ttf)
go installFontZIP(msg, m)
}()
m.spinner.Spinner = spinner.Dot
return m, m.spinner.Tick
Expand Down Expand Up @@ -288,6 +298,10 @@ func (m *main) View() string {
case quit:
return textStyle.Render(fmt.Sprintf("No need to install a new font? That's cool.%s", terminal.StopProgress()))
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

builder.WriteString(fmt.Sprintf("Successfully installed %s 🚀\n\n%s", m.font, terminal.StopProgress()))
Expand All @@ -307,11 +321,11 @@ func (m *main) View() string {
return ""
}

func Run(font string, ch cache_.Cache, root, ttf bool) {
func Run(font string, ch cache_.Cache, root bool, zipFolder string) {
main := &main{
font: font,
system: root,
ttf: ttf,
font: font,
system: root,
zipFolder: zipFolder,
}

cache = ch
Expand Down
52 changes: 28 additions & 24 deletions src/font/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,7 @@ func contains[S ~[]E, E comparable](s S, e E) bool {
return false
}

func InstallZIP(data []byte, user, ttf bool) ([]string, error) {
// prefer OTF over TTF; otherwise prefer the first font we find
extension := ".otf"
if ttf {
extension = ".ttf"
}

func InstallZIP(data []byte, m *main) ([]string, error) {
var families []string
bytesReader := bytes.NewReader(data)

Expand All @@ -42,46 +36,56 @@ func InstallZIP(data []byte, user, ttf bool) ([]string, error) {

fonts := make(map[string]*Font)

for _, zf := range zipReader.File {
root := len(m.zipFolder) == 0

for _, file := range zipReader.File {
// prevent zipslip attacks
// 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
}

fontFileName := path.Base(file.Name)

// do not install fonts that are not in the root folder when specified as such
if root && fontFileName != file.Name {
continue
}

rc, err := zf.Open()
fontReader, err := file.Open()
if err != nil {
return families, err
continue
}

defer rc.Close()
defer fontReader.Close()

data, err := io.ReadAll(rc)
fontBytes, err := io.ReadAll(fontReader)
if err != nil {
return families, err
continue
}

fontData, err := newFont(path.Base(zf.Name), data)
font, err := newFont(fontFileName, fontBytes)
if err != nil {
continue
}

if _, found := fonts[fontData.Name]; !found {
fonts[fontData.Name] = fontData
if _, found := fonts[font.Name]; !found {
fonts[font.Name] = font
continue
}

// respect the user's preference for TTF or OTF
first := strings.ToLower(path.Ext(fonts[fontData.Name].FileName))
second := strings.ToLower(path.Ext(fontData.FileName))
if first != second && second == extension {
fonts[fontData.Name] = fontData
// 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 {
if err = install(font, user); err != nil {
return families, err
if err = install(font, m.system); err != nil {
continue
}

if found := contains(families, font.Family); !found {
Expand Down
5 changes: 3 additions & 2 deletions website/docs/installation/fonts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Oh My Posh has a CLI to help you select and install a [Nerd Font][nerdfonts]:
:::info
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.
By default, Oh My Posh installs the `.ttf` version of the font in case multiple versions are available.
:::

```bash
Expand All @@ -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
```

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
oh-my-posh font install meslo --ttf
oh-my-posh font install --zip-folder ttf/static CascadiaCode-2407.24
```

</TabItem>
Expand Down

0 comments on commit a7ad857

Please sign in to comment.