Support `pager` and `no-pager` config options

pull/6/head
Mickaël Menu 3 years ago
parent b86a74d1e9
commit a691a8857c
No known key found for this signature in database
GPG Key ID: 53D73664CD359895

@ -147,7 +147,54 @@ func (d *NoteDAO) exists(path string) (bool, error) {
}
func (d *NoteDAO) Find(opts note.FinderOpts, callback func(note.Match) error) (int, error) {
rows, err := func() (*sql.Rows, error) {
rows, err := d.findRows(opts)
if err != nil {
return 0, err
}
defer rows.Close()
count := 0
for rows.Next() {
count++
var (
id, wordCount int
title, body, snippet string
path, checksum string
created, modified time.Time
)
err := rows.Scan(&id, &path, &title, &body, &wordCount, &created, &modified, &checksum, &snippet)
if err != nil {
d.logger.Err(err)
continue
}
callback(note.Match{
Snippet: snippet,
Metadata: note.Metadata{
Path: path,
Title: title,
Body: body,
WordCount: wordCount,
Created: created,
Modified: modified,
Checksum: checksum,
},
})
}
return count, nil
}
type findQuery struct {
SnippetCol string
WhereExprs []string
OrderTerms []string
Args []interface{}
}
func (d *NoteDAO) findRows(opts note.FinderOpts) (*sql.Rows, error) {
snippetCol := `""`
whereExprs := make([]string, 0)
orderTerms := make([]string, 0)
@ -223,46 +270,9 @@ ON n.id = notes_fts.rowid`
query += fmt.Sprintf("\nLIMIT %d", opts.Limit)
}
// fmt.Println(query)
// fmt.Println(args)
return d.tx.Query(query, args...)
}()
if err != nil {
return 0, err
}
defer rows.Close()
count := 0
for rows.Next() {
count++
var (
id, wordCount int
title, body, snippet string
path, checksum string
created, modified time.Time
)
err := rows.Scan(&id, &path, &title, &body, &wordCount, &created, &modified, &checksum, &snippet)
if err != nil {
d.logger.Err(err)
continue
}
callback(note.Match{
Snippet: snippet,
Metadata: note.Metadata{
Path: path,
Title: title,
Body: body,
WordCount: wordCount,
Created: created,
Modified: modified,
Checksum: checksum,
},
})
}
return count, nil
}
func dateField(filter note.DateFilter) string {

@ -1,11 +1,15 @@
package cmd
import (
"io"
"github.com/mickael-menu/zk/adapter/handlebars"
"github.com/mickael-menu/zk/adapter/sqlite"
"github.com/mickael-menu/zk/adapter/tty"
"github.com/mickael-menu/zk/core/zk"
"github.com/mickael-menu/zk/util"
"github.com/mickael-menu/zk/util/date"
"github.com/mickael-menu/zk/util/pager"
)
type Container struct {
@ -43,3 +47,25 @@ func (c *Container) Database(path string) (*sqlite.DB, error) {
err = db.Migrate()
return db, err
}
// Paginate creates an auto-closing io.Writer which will be automatically
// paginated if noPager is false, using the user's pager.
//
// You can write to the pager only in the run callback.
func (c *Container) Paginate(noPager bool, config zk.Config, run func(out io.Writer) error) error {
pager, err := c.pager(noPager || config.NoPager, config)
if err != nil {
return err
}
err = run(pager)
pager.Close()
return err
}
func (c *Container) pager(noPager bool, config zk.Config) (*pager.Pager, error) {
if noPager {
return pager.PassthroughPager, nil
} else {
return pager.New(config.Pager, c.Logger)
}
}

@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"io"
"time"
"github.com/mickael-menu/zk/adapter/sqlite"
@ -9,7 +10,6 @@ import (
"github.com/mickael-menu/zk/core/zk"
"github.com/mickael-menu/zk/util/errors"
"github.com/mickael-menu/zk/util/opt"
"github.com/mickael-menu/zk/util/pager"
"github.com/mickael-menu/zk/util/strings"
"github.com/tj/go-naturaldate"
)
@ -58,17 +58,11 @@ func (cmd *List) Run(container *Container) error {
Templates: container.TemplateLoader(zk.Config.Lang),
}
p := pager.PassthroughPager
if !cmd.NoPager {
p, err = pager.New(logger)
if err != nil {
count := 0
err = container.Paginate(cmd.NoPager, zk.Config, func(out io.Writer) error {
count, err = note.List(*opts, deps, out)
return err
}
}
count, err := note.List(*opts, deps, p.WriteString)
p.Close()
})
if err == nil {
fmt.Printf("\nFound %d %s\n", count, strings.Pluralize("result", count))

@ -1,6 +1,8 @@
package note
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
@ -24,7 +26,7 @@ type ListDeps struct {
// List finds notes matching given criteria and formats them according to user
// preference.
func List(opts ListOpts, deps ListDeps, callback func(formattedNote string) error) (int, error) {
func List(opts ListOpts, deps ListDeps, out io.Writer) (int, error) {
templ := matchTemplate(opts.Format)
template, err := deps.Templates.Load(templ)
if err != nil {
@ -40,7 +42,9 @@ func List(opts ListOpts, deps ListDeps, callback func(formattedNote string) erro
if err != nil {
return err
}
return callback(res)
_, err = fmt.Fprintln(out, res)
return err
})
}

@ -13,6 +13,8 @@ type Config struct {
DirConfig
Dirs map[string]DirConfig
Editor opt.String
Pager opt.String
NoPager bool
}
// DirConfig holds the user configuration for a given directory.
@ -115,6 +117,8 @@ func ParseConfig(content []byte, templatesDir string) (*Config, error) {
DirConfig: root,
Dirs: make(map[string]DirConfig),
Editor: opt.NewNotEmptyString(hcl.Editor),
Pager: opt.NewNotEmptyString(hcl.Pager),
NoPager: hcl.NoPager,
}
for _, dirHCL := range hcl.Dirs {
@ -183,6 +187,8 @@ type hclConfig struct {
Extra map[string]string `hcl:"extra,optional"`
Dirs []hclDirConfig `hcl:"dir,block"`
Editor string `hcl:"editor,optional"`
Pager string `hcl:"pager,optional"`
NoPager bool `hcl:"no-pager,optional"`
}
type hclDirConfig struct {

@ -5,8 +5,8 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"github.com/mickael-menu/zk/util/test/assert"
"github.com/mickael-menu/zk/util/opt"
"github.com/mickael-menu/zk/util/test/assert"
)
func TestParseDefaultConfig(t *testing.T) {
@ -43,6 +43,8 @@ func TestParseComplete(t *testing.T) {
conf, err := ParseConfig([]byte(`
// Comment
editor = "vim"
pager = "less"
no-pager = true
filename = "{{id}}.note"
extension = "txt"
template = "default.note"
@ -131,6 +133,8 @@ func TestParseComplete(t *testing.T) {
},
},
Editor: opt.NewString("vim"),
Pager: opt.NewString("less"),
NoPager: true,
})
}

@ -56,7 +56,8 @@ func (s String) Unwrap() string {
}
func (s String) Equal(other String) bool {
return s.value == other.value || *s.value == *other.value
return s.value == other.value ||
(s.value != nil && other.value != nil && *s.value == *other.value)
}
func (s String) String() string {

@ -31,10 +31,10 @@ var PassthroughPager = &Pager{
}
// New creates a pager.Pager to be used to write a paginated text to the TTY.
func New(logger util.Logger) (*Pager, error) {
func New(pagerCmd opt.String, logger util.Logger) (*Pager, error) {
wrap := errors.Wrapper("failed to paginate the output, try again with --no-pager or fix your PAGER environment variable")
pagerCmd := locatePager()
pagerCmd = selectPagerCmd(pagerCmd)
if pagerCmd.IsNull() {
return PassthroughPager, nil
}
@ -98,17 +98,24 @@ func (p *Pager) WriteString(text string) error {
return err
}
func locatePager() opt.String {
// selectPagerCmd returns the paging command meant to be run.
//
// By order of precedence: ZK_PAGER, config.pager, PAGER then the default
// pagers.
func selectPagerCmd(userPager opt.String) opt.String {
return osutil.GetOptEnv("ZK_PAGER").
Or(userPager).
Or(osutil.GetOptEnv("PAGER")).
Or(locateDefaultPager())
Or(selectDefaultPager())
}
var defaultPagers = []string{
"less -FIRX", "more -R",
}
func locateDefaultPager() opt.String {
// selectDefaultPager returns the first pager in the list of defaultPagers
// available on the execution paths.
func selectDefaultPager() opt.String {
for _, pager := range defaultPagers {
parts, err := shellquote.Split(pager)
if err != nil {

Loading…
Cancel
Save