diff --git a/config_cmd.go b/config_cmd.go index 7147ab8..390a1db 100644 --- a/config_cmd.go +++ b/config_cmd.go @@ -39,13 +39,13 @@ var configCmd = &cobra.Command{ c, err := editor.Cmd("Glow", configFile) if err != nil { - return err + return fmt.Errorf("unable to set config file: %w", err) } c.Stdin = os.Stdin c.Stdout = os.Stdout c.Stderr = os.Stderr if err := c.Run(); err != nil { - return err + return fmt.Errorf("unable to run command: %w", err) } fmt.Println("Wrote config file to:", configFile) @@ -56,7 +56,7 @@ var configCmd = &cobra.Command{ func ensureConfigFile() error { if configFile == "" { configFile = viper.GetViper().ConfigFileUsed() - if err := os.MkdirAll(filepath.Dir(configFile), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(configFile), 0o755); err != nil { //nolint:gosec return fmt.Errorf("could not write configuration file: %w", err) } } @@ -69,20 +69,20 @@ func ensureConfigFile() error { // File doesn't exist yet, create all necessary directories and // write the default config file if err := os.MkdirAll(filepath.Dir(configFile), 0o700); err != nil { - return err + return fmt.Errorf("unable create directory: %w", err) } f, err := os.Create(configFile) if err != nil { - return err + return fmt.Errorf("unable to create config file: %w", err) } defer func() { _ = f.Close() }() if _, err := f.WriteString(defaultConfig); err != nil { - return err + return fmt.Errorf("unable to write config file: %w", err) } } else if err != nil { // some other error occurred - return err + return fmt.Errorf("unable to stat config file: %w", err) } return nil } diff --git a/github.go b/github.go index 6b169fa..fe862e3 100644 --- a/github.go +++ b/github.go @@ -23,29 +23,29 @@ func findGitHubREADME(u *url.URL) (*source, error) { apiURL := fmt.Sprintf("https://api.%s/repos/%s/%s/readme", u.Hostname(), owner, repo) - // nolint:bodyclose + //nolint:bodyclose // it is closed on the caller - res, err := http.Get(apiURL) // nolint: gosec + res, err := http.Get(apiURL) //nolint: gosec,noctx if err != nil { - return nil, err + return nil, fmt.Errorf("unable to get url: %w", err) } body, err := io.ReadAll(res.Body) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to read http response body: %w", err) } var result readme if err := json.Unmarshal(body, &result); err != nil { - return nil, err + return nil, fmt.Errorf("unable to parse json: %w", err) } if res.StatusCode == http.StatusOK { - // nolint:bodyclose + //nolint:bodyclose // it is closed on the caller - resp, err := http.Get(result.DownloadURL) + resp, err := http.Get(result.DownloadURL) //nolint: noctx if err != nil { - return nil, err + return nil, fmt.Errorf("unable to get url: %w", err) } if resp.StatusCode == http.StatusOK { diff --git a/gitlab.go b/gitlab.go index 05e1239..68256be 100644 --- a/gitlab.go +++ b/gitlab.go @@ -25,31 +25,31 @@ func findGitLabREADME(u *url.URL) (*source, error) { apiURL := fmt.Sprintf("https://%s/api/v4/projects/%s", u.Hostname(), projectPath) - // nolint:bodyclose + //nolint:bodyclose // it is closed on the caller - res, err := http.Get(apiURL) // nolint: gosec + res, err := http.Get(apiURL) //nolint: gosec,noctx if err != nil { - return nil, err + return nil, fmt.Errorf("unable to get url: %w", err) } body, err := io.ReadAll(res.Body) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to read http response body: %w", err) } var result readme if err := json.Unmarshal(body, &result); err != nil { - return nil, err + return nil, fmt.Errorf("unable to parse json: %w", err) } - readmeRawURL := strings.Replace(result.ReadmeURL, "blob", "raw", -1) + readmeRawURL := strings.ReplaceAll(result.ReadmeURL, "blob", "raw") if res.StatusCode == http.StatusOK { - // nolint:bodyclose + //nolint:bodyclose // it is closed on the caller - resp, err := http.Get(readmeRawURL) // nolint: gosec + resp, err := http.Get(readmeRawURL) //nolint: gosec,noctx if err != nil { - return nil, err + return nil, fmt.Errorf("unable to get url: %w", err) } if resp.StatusCode == http.StatusOK { diff --git a/log.go b/log.go index 531feac..a864a71 100644 --- a/log.go +++ b/log.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "io" "os" "path/filepath" @@ -12,7 +13,7 @@ import ( func getLogFilePath() (string, error) { dir, err := gap.NewScope(gap.User, "glow").CacheDir() if err != nil { - return "", err + return "", fmt.Errorf("unable to get cache dir: %w", err) } return filepath.Join(dir, "glow.log"), nil } @@ -24,14 +25,14 @@ func setupLog() (func() error, error) { if err != nil { return nil, err } - if err := os.MkdirAll(filepath.Dir(logFile), 0o755); err != nil { + if err := os.MkdirAll(filepath.Dir(logFile), 0o755); err != nil { //nolint:gosec // log disabled - return func() error { return nil }, nil + return func() error { return nil }, nil //nolint:nilerr } - f, err := os.OpenFile(logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) + f, err := os.OpenFile(logFile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0o644) //nolint:gosec if err != nil { // log disabled - return func() error { return nil }, nil + return func() error { return nil }, nil //nolint:nilerr } log.SetOutput(f) log.SetLevel(log.DebugLevel) diff --git a/main.go b/main.go index 02209fa..c1643b5 100644 --- a/main.go +++ b/main.go @@ -1,3 +1,4 @@ +// Package main provides the entry point for the Glow CLI application. package main import ( @@ -83,15 +84,15 @@ func sourceFromArg(arg string) (*source, error) { } // HTTP(S) URLs: - if u, err := url.ParseRequestURI(arg); err == nil && strings.Contains(arg, "://") { + if u, err := url.ParseRequestURI(arg); err == nil && strings.Contains(arg, "://") { //nolint:nestif if u.Scheme != "" { if u.Scheme != "http" && u.Scheme != "https" { return nil, fmt.Errorf("%s is not a supported protocol", u.Scheme) } // consumer of the source is responsible for closing the ReadCloser. - resp, err := http.Get(u.String()) // nolint:bodyclose + resp, err := http.Get(u.String()) //nolint: noctx,bodyclose if err != nil { - return nil, err + return nil, fmt.Errorf("unable to get url: %w", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP status %d", resp.StatusCode) @@ -106,7 +107,7 @@ func sourceFromArg(arg string) (*source, error) { arg = "." } st, err := os.Stat(arg) - if err == nil && st.IsDir() { + if err == nil && st.IsDir() { //nolint:nestif var src *source _ = filepath.Walk(arg, func(path string, _ os.FileInfo, err error) error { if err != nil { @@ -136,10 +137,15 @@ func sourceFromArg(arg string) (*source, error) { return nil, errors.New("missing markdown source") } - // a file: r, err := os.Open(arg) - u, _ := filepath.Abs(arg) - return &source{r, u}, err + if err != nil { + return nil, fmt.Errorf("unable to open file: %w", err) + } + u, err := filepath.Abs(arg) + if err != nil { + return nil, fmt.Errorf("unable to get absolute path: %w", err) + } + return &source{r, u}, nil } // validateStyle checks if the style is a default style, if not, checks that @@ -148,9 +154,9 @@ func validateStyle(style string) error { if style != "auto" && styles.DefaultStyles[style] == nil { style = utils.ExpandPath(style) if _, err := os.Stat(style); errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("Specified style does not exist: %s", style) + return fmt.Errorf("specified style does not exist: %s", style) } else if err != nil { - return err + return fmt.Errorf("unable to stat file: %w", err) } } return nil @@ -166,7 +172,7 @@ func validateOptions(cmd *cobra.Command) error { preserveNewLines = viper.GetBool("preserveNewLines") if pager && tui { - return errors.New("glow: cannot use both pager and tui") + return errors.New("cannot use both pager and tui") } // validate the glamour style @@ -183,11 +189,11 @@ func validateOptions(cmd *cobra.Command) error { } // Detect terminal width - if !cmd.Flags().Changed("width") { + if !cmd.Flags().Changed("width") { //nolint:nestif if isTerminal && width == 0 { w, _, err := term.GetSize(int(os.Stdout.Fd())) if err == nil { - width = uint(w) + width = uint(w) //nolint:gosec } if width > 120 { @@ -204,7 +210,7 @@ func validateOptions(cmd *cobra.Command) error { func stdinIsPipe() (bool, error) { stat, err := os.Stdin.Stat() if err != nil { - return false, err + return false, fmt.Errorf("unable to open file: %w", err) } if stat.Mode()&os.ModeCharDevice == 0 || stat.Size() > 0 { return true, nil @@ -266,7 +272,7 @@ func executeArg(cmd *cobra.Command, arg string, w io.Writer) error { func executeCLI(cmd *cobra.Command, src *source, w io.Writer) error { b, err := io.ReadAll(src.reader) if err != nil { - return err + return fmt.Errorf("unable to read from reader: %w", err) } b = utils.RemoveFrontmatter(b) @@ -285,12 +291,12 @@ func executeCLI(cmd *cobra.Command, src *source, w io.Writer) error { r, err := glamour.NewTermRenderer( glamour.WithColorProfile(lipgloss.ColorProfile()), utils.GlamourStyle(style, isCode), - glamour.WithWordWrap(int(width)), + glamour.WithWordWrap(int(width)), //nolint:gosec glamour.WithBaseURL(baseURL), glamour.WithPreservedNewLines(), ) if err != nil { - return err + return fmt.Errorf("unable to create renderer: %w", err) } content := string(b) @@ -301,7 +307,7 @@ func executeCLI(cmd *cobra.Command, src *source, w io.Writer) error { out, err := r.Render(content) if err != nil { - return err + return fmt.Errorf("unable to render markdown: %w", err) } // display @@ -313,15 +319,20 @@ func executeCLI(cmd *cobra.Command, src *source, w io.Writer) error { } pa := strings.Split(pagerCmd, " ") - c := exec.Command(pa[0], pa[1:]...) // nolint:gosec + c := exec.Command(pa[0], pa[1:]...) //nolint:gosec c.Stdin = strings.NewReader(out) c.Stdout = os.Stdout - return c.Run() + if err := c.Run(); err != nil { + return fmt.Errorf("unable to run command: %w", err) + } + return nil case tui || cmd.Flags().Changed("tui"): return runTUI(src.URL, content) default: - _, err = fmt.Fprint(w, out) - return err + if _, err = fmt.Fprint(w, out); err != nil { + return fmt.Errorf("unable to write to writer: %w", err) + } + return nil } } @@ -346,7 +357,7 @@ func runTUI(path string, content string) error { // Run Bubble Tea program if _, err := ui.NewProgram(cfg, content).Run(); err != nil { - return err + return fmt.Errorf("unable to run tui program: %w", err) } return nil diff --git a/man_cmd.go b/man_cmd.go index 9aa5951..a179ef7 100644 --- a/man_cmd.go +++ b/man_cmd.go @@ -19,9 +19,11 @@ var manCmd = &cobra.Command{ RunE: func(*cobra.Command, []string) error { manPage, err := mcobra.NewManPage(1, rootCmd) if err != nil { - return err + return fmt.Errorf("unable to instantiate man page: %w", err) } - _, err = fmt.Fprint(os.Stdout, manPage.Build(roff.NewDocument())) - return err + if _, err := fmt.Fprint(os.Stdout, manPage.Build(roff.NewDocument())); err != nil { + return fmt.Errorf("unable to build man page: %w", err) + } + return nil }, } diff --git a/ui/editor.go b/ui/editor.go index 5eb2a06..a567c59 100644 --- a/ui/editor.go +++ b/ui/editor.go @@ -11,7 +11,7 @@ func openEditor(path string, lineno int) tea.Cmd { cb := func(err error) tea.Msg { return editorFinishedMsg{err} } - cmd, err := editor.Cmd("Glow", path, editor.OpenAtLine(uint(lineno))) + cmd, err := editor.Cmd("Glow", path, editor.LineNumber(uint(lineno))) //nolint:gosec if err != nil { return func() tea.Msg { return cb(err) } } diff --git a/ui/markdown.go b/ui/markdown.go index 8bafad1..c67b1dc 100644 --- a/ui/markdown.go +++ b/ui/markdown.go @@ -1,6 +1,7 @@ package ui import ( + "fmt" "math" "time" "unicode" @@ -48,7 +49,10 @@ func (m markdown) relativeTime() string { func normalize(in string) (string, error) { t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) out, _, err := transform.String(t, in) - return out, err + if err != nil { + return "", fmt.Errorf("error normalizing: %w", err) + } + return out, nil } // Return the time in a human-readable format relative to the current time. diff --git a/ui/pager.go b/ui/pager.go index 207fafc..ff067f7 100644 --- a/ui/pager.go +++ b/ui/pager.go @@ -317,7 +317,7 @@ func (m pagerModel) statusBarView(b *strings.Builder) { } else { note = m.currentDocument.Note } - note = truncate.StringWithTail(" "+note+" ", uint(max(0, + note = truncate.StringWithTail(" "+note+" ", uint(max(0, //nolint:gosec m.common.width- ansi.PrintableRuneWidth(logo)- ansi.PrintableRuneWidth(scrollPercent)- @@ -415,7 +415,7 @@ func glamourRender(m pagerModel, markdown string) (string, error) { } isCode := !utils.IsMarkdownFile(m.currentDocument.Note) - width := max(0, min(int(m.common.cfg.GlamourMaxWidth), m.viewport.Width)) + width := max(0, min(int(m.common.cfg.GlamourMaxWidth), m.viewport.Width)) //nolint:gosec if isCode { width = 0 } @@ -430,7 +430,7 @@ func glamourRender(m pagerModel, markdown string) (string, error) { } r, err := glamour.NewTermRenderer(options...) if err != nil { - return "", err + return "", fmt.Errorf("error creating glamour renderer: %w", err) } if isCode { @@ -439,7 +439,7 @@ func glamourRender(m pagerModel, markdown string) (string, error) { out, err := r.Render(markdown) if err != nil { - return "", err + return "", fmt.Errorf("error rendering markdown: %w", err) } if isCode { diff --git a/ui/stash.go b/ui/stash.go index 2021966..85b76b5 100644 --- a/ui/stash.go +++ b/ui/stash.go @@ -126,7 +126,7 @@ func initSections() { // String returns a styled version of the status message appropriate for the // given context. func (s statusMessage) String() string { - switch s.status { + switch s.status { //nolint:exhaustive case subtleStatusMessage: return dimGreenFg(s.message) case errorStatusMessage: @@ -444,7 +444,7 @@ func (m stashModel) update(msg tea.Msg) (stashModel, tea.Cmd) { } // Updates per the current state - switch m.viewState { + switch m.viewState { //nolint:exhaustive case stashStateReady: cmds = append(cmds, m.handleDocumentBrowsing(msg)) case stashStateShowingError: @@ -596,7 +596,7 @@ func (m *stashModel) handleFiltering(msg tea.Msg) tea.Cmd { var cmds []tea.Cmd // Handle keys - if msg, ok := msg.(tea.KeyMsg); ok { + if msg, ok := msg.(tea.KeyMsg); ok { //nolint:nestif switch msg.String() { case keyEsc: // Cancel filtering @@ -690,7 +690,7 @@ func (m stashModel) view() string { logoOrFilter += " " + m.statusMessage.String() } } - logoOrFilter = truncate.StringWithTail(logoOrFilter, uint(m.common.width-1), ellipsis) + logoOrFilter = truncate.StringWithTail(logoOrFilter, uint(m.common.width-1), ellipsis) //nolint:gosec help, helpHeight := m.helpView() diff --git a/ui/stashitem.go b/ui/stashitem.go index 26e8bb6..0000928 100644 --- a/ui/stashitem.go +++ b/ui/stashitem.go @@ -17,7 +17,7 @@ const ( func stashItemView(b *strings.Builder, m stashModel, index int, md *markdown) { var ( - truncateTo = uint(m.common.width - stashViewHorizontalPadding*2) + truncateTo = uint(m.common.width - stashViewHorizontalPadding*2) //nolint:gosec gutter string title = truncate.StringWithTail(md.Note, truncateTo, ellipsis) date = md.relativeTime() @@ -34,7 +34,7 @@ func stashItemView(b *strings.Builder, m stashModel, index int, md *markdown) { // If there are multiple items being filtered don't highlight a selected // item in the results. If we've filtered down to one item, however, // highlight that first item since pressing return will open it. - if isSelected && !isFiltering || singleFilteredItem { + if isSelected && !isFiltering || singleFilteredItem { //nolint:nestif // Selected item if m.statusMessage == stashingStatusMessage { gutter = greenFg(verticalLine) diff --git a/ui/ui.go b/ui/ui.go index eeda361..3537d3f 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -1,3 +1,4 @@ +// Package ui provides the main UI for the glow application. package ui import ( @@ -120,7 +121,7 @@ func (m *model) unloadDocument() []tea.Cmd { var batch []tea.Cmd if m.pager.viewport.HighPerformanceRendering { - batch = append(batch, tea.ClearScrollArea) + batch = append(batch, tea.ClearScrollArea) //nolint:staticcheck } if !m.stash.shouldSpin() { @@ -234,7 +235,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "q": var cmd tea.Cmd - switch m.state { + switch m.state { //nolint:exhaustive case stateShowStash: // pass through all keys if we're editing the filter if m.stash.filterState == filtering { @@ -328,7 +329,7 @@ func (m model) View() string { return errorView(m.fatalErr, true) } - switch m.state { + switch m.state { //nolint:exhaustive case stateShowDocument: return m.pager.View() default: @@ -449,17 +450,3 @@ func indent(s string, n int) string { } return b.String() } - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} diff --git a/url.go b/url.go index 43ca167..8430d5a 100644 --- a/url.go +++ b/url.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "net/url" "strings" "sync" @@ -44,7 +45,7 @@ func readmeURL(path string) (*source, error) { } u, err := url.Parse(path) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to parse url: %w", err) } switch { diff --git a/utils/utils.go b/utils/utils.go index 8240c36..9f33a1e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,3 +1,4 @@ +// Package utils provides utility functions. package utils import ( @@ -30,7 +31,7 @@ func detectFrontmatter(c []byte) []int { return []int{-1, -1} } -// Expands tilde and all environment variables from the given path. +// ExpandPath expands tilde and all environment variables from the given path. func ExpandPath(path string) string { s, err := homedir.Expand(path) if err == nil { @@ -68,13 +69,13 @@ func IsMarkdownFile(filename string) bool { return false } +// GlamourStyle returns a glamour.TermRendererOption based on the given style. func GlamourStyle(style string, isCode bool) glamour.TermRendererOption { if !isCode { if style == styles.AutoStyle { return glamour.WithAutoStyle() - } else { - return glamour.WithStylePath(style) } + return glamour.WithStylePath(style) } // If we are rendering a pure code block, we need to modify the style to