feat: parse all Go CLIs with flaggy; dump bash complete.
stdlib flag dropped --hop after the query. One wrapper in internal/cli, source <(./bin/cli/complete.go bash). Gitea #34.
This commit is contained in:
Executable
+91
@@ -0,0 +1,91 @@
|
||||
//usr/bin/env go run "$0" "$@"; exit
|
||||
//
|
||||
// bin/cli/complete.go - dump flaggy shell completions for all Go shebang tools (D23).
|
||||
//
|
||||
// source <(./bin/cli/complete.go bash)
|
||||
// ./bin/cli/complete.go zsh|fish|powershell|nushell
|
||||
//
|
||||
// Search does not steal the word "completion"; this binary dumps scripts.
|
||||
// NOTE: never run `gofmt -w` on this file — it breaks the shebang.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
mailsync "github.com/eSlider/2dph/bin/mail/sync"
|
||||
"github.com/eSlider/2dph/internal/brain/rank"
|
||||
"github.com/eSlider/2dph/internal/chats"
|
||||
"github.com/eSlider/2dph/internal/cli"
|
||||
"github.com/eSlider/2dph/internal/gitlog"
|
||||
"github.com/eSlider/2dph/internal/mdleaves"
|
||||
"github.com/eSlider/2dph/internal/ocr"
|
||||
"github.com/eSlider/2dph/internal/reasoner"
|
||||
"github.com/eSlider/2dph/internal/websearch"
|
||||
"github.com/integrii/flaggy"
|
||||
)
|
||||
|
||||
func tools() []cli.Tool {
|
||||
return []cli.Tool{
|
||||
{Path: "bin/brain/search.go", Name: "brain-search", New: rank.Parser},
|
||||
{Path: "bin/brain/get.go", Name: "brain-get", New: func() *flaggy.Parser {
|
||||
o := rank.GetOptions{}
|
||||
return rank.GetParser(&o)
|
||||
}},
|
||||
{Path: "bin/brain/stats.go", Name: "brain-stats", New: rank.StatsParser},
|
||||
{Path: "bin/brain/eval.go", Name: "brain-eval", New: rank.EvalParser},
|
||||
{Path: "bin/web/search.go", Name: "web-search", New: websearch.Parser},
|
||||
{Path: "bin/git/import.go", Name: "git-import", New: gitlog.Parser},
|
||||
{Path: "bin/markdown/import.go", Name: "markdown-import", New: mdleaves.Parser},
|
||||
{Path: "bin/qa/stats.go", Name: "qa-stats", New: cli.QAParser},
|
||||
{Path: "bin/reasoner/bakeoff.go", Name: "reasoner-bakeoff", New: reasoner.Parser},
|
||||
{Path: "bin/mail/ocr.go", Name: "mail-ocr", New: ocr.Parser},
|
||||
{Path: "bin/mail/sync.go", Name: "mail-sync", New: mailsync.Parser},
|
||||
{Path: "bin/chats/sync.go", Name: "chats-sync", New: chats.SyncParser},
|
||||
{Path: "bin/chats/import.go", Name: "chats-import", New: chats.ImportParser},
|
||||
{Path: "bin/chats/facts.go", Name: "chats-facts", New: chats.FactsParser},
|
||||
{Path: "bin/chats/apply.go", Name: "chats-apply", New: chats.ApplyParser},
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
os.Exit(run(os.Args[1:]))
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
shell := "bash"
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "bash", "zsh", "fish", "powershell", "nushell":
|
||||
shell = args[0]
|
||||
case "-h", "--help", "help":
|
||||
fmt.Fprintln(os.Stderr, "usage: bin/cli/complete.go [bash|zsh|fish|powershell|nushell]")
|
||||
return 0
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "cli/complete: unknown shell %q\n", args[0])
|
||||
return 2
|
||||
}
|
||||
}
|
||||
if shell == "bash" {
|
||||
fmt.Print(cli.BashScript(tools()))
|
||||
return 0
|
||||
}
|
||||
var b strings.Builder
|
||||
for _, t := range tools() {
|
||||
p := t.New()
|
||||
p.Name = t.Name
|
||||
switch shell {
|
||||
case "zsh":
|
||||
b.WriteString(flaggy.GenerateZshCompletion(p))
|
||||
case "fish":
|
||||
b.WriteString(flaggy.GenerateFishCompletion(p))
|
||||
case "powershell":
|
||||
b.WriteString(flaggy.GeneratePowerShellCompletion(p))
|
||||
case "nushell":
|
||||
b.WriteString(flaggy.GenerateNushellCompletion(p))
|
||||
}
|
||||
}
|
||||
fmt.Print(b.String())
|
||||
return 0
|
||||
}
|
||||
+9
-52
@@ -16,9 +16,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
cliparse "github.com/eSlider/2dph/internal/cli"
|
||||
"github.com/eSlider/2dph/internal/cmdbin"
|
||||
"github.com/eSlider/2dph/internal/gitlog"
|
||||
)
|
||||
@@ -28,49 +27,16 @@ func main() {
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
var repo, root, since string
|
||||
limit := 0
|
||||
jsonOut := false
|
||||
i := 0
|
||||
for i < len(args) {
|
||||
a := args[i]
|
||||
switch {
|
||||
case a == "--json":
|
||||
jsonOut = true
|
||||
case a == "--limit" && i+1 < len(args):
|
||||
i++
|
||||
n, err := strconv.Atoi(args[i])
|
||||
if err != nil || n < 0 {
|
||||
fmt.Fprintf(os.Stderr, "git/import: --limit must be a non-negative integer\n")
|
||||
return 2
|
||||
}
|
||||
limit = n
|
||||
case a == "--since" && i+1 < len(args):
|
||||
i++
|
||||
since = args[i]
|
||||
case a == "--root" && i+1 < len(args):
|
||||
i++
|
||||
root = args[i]
|
||||
case a == "-h" || a == "--help":
|
||||
fmt.Fprintln(os.Stderr, `usage: bin/git/import.go [REPO] [--json] [--limit N] [--since DATE] [--root DIR]`)
|
||||
return 0
|
||||
case len(a) > 0 && a[0] != '-':
|
||||
repo = a
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "git/import: unknown flag %s\n", a)
|
||||
return 2
|
||||
}
|
||||
i++
|
||||
c, err := gitlog.ParseArgs(args)
|
||||
if err != nil {
|
||||
return cliparse.Fail(err)
|
||||
}
|
||||
repo, root, since, limit, jsonOut := c.Repo, c.Root, c.Since, c.Limit, c.JSONOut
|
||||
|
||||
var sinceT time.Time
|
||||
if since != "" {
|
||||
var err error
|
||||
sinceT, err = parseSince(since)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "git/import: %v\n", err)
|
||||
return 2
|
||||
}
|
||||
sinceT, err := gitlog.ParseSince(since)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "git/import: %v\n", err)
|
||||
return 2
|
||||
}
|
||||
|
||||
repos := []string{}
|
||||
@@ -132,12 +98,3 @@ func run(args []string) int {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func parseSince(s string) (time.Time, error) {
|
||||
for _, layout := range []string{time.RFC3339, "2006-01-02"} {
|
||||
if t, err := time.Parse(layout, s); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("cannot parse --since %q", s)
|
||||
}
|
||||
|
||||
+6
-8
@@ -17,6 +17,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
cliparse "github.com/eSlider/2dph/internal/cli"
|
||||
"github.com/eSlider/2dph/internal/ocr"
|
||||
)
|
||||
|
||||
@@ -25,15 +26,12 @@ func main() {
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
if len(args) != 1 || strings.HasPrefix(args[0], "-") {
|
||||
fmt.Fprintln(os.Stderr, `usage: bin/mail/ocr.go <image|pdf>`)
|
||||
return 2
|
||||
c, err := ocr.ParseArgs(args)
|
||||
if err != nil {
|
||||
return cliparse.Fail(err)
|
||||
}
|
||||
path := args[0]
|
||||
var (
|
||||
text string
|
||||
err error
|
||||
)
|
||||
path := c.Path
|
||||
var text string
|
||||
if strings.HasSuffix(strings.ToLower(path), ".pdf") {
|
||||
text, err = ocr.PDFFile(path)
|
||||
} else {
|
||||
|
||||
+71
-43
@@ -5,74 +5,103 @@ package sync
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cliparse "github.com/eSlider/2dph/internal/cli"
|
||||
"github.com/integrii/flaggy"
|
||||
)
|
||||
|
||||
// CLIConfig is a superset of SyncConfig plus flag parsing results.
|
||||
type CLIConfig struct {
|
||||
Sync SyncConfig
|
||||
Env string // .env path; default <cwd>/.env
|
||||
Sync SyncConfig
|
||||
Env string // .env path; default <cwd>/.env
|
||||
Sources string
|
||||
Help bool
|
||||
Help bool
|
||||
}
|
||||
|
||||
// ParseCLI reads os.Args into a CLIConfig. Exit codes: 0 ok, 2 usage.
|
||||
type flagVals struct {
|
||||
env, out, srcs, query string
|
||||
workers, limit, offset int
|
||||
force, dryRun bool
|
||||
}
|
||||
|
||||
func Parser() *flaggy.Parser {
|
||||
v := flagVals{workers: 4, query: "in:inbox", srcs: "onlyoffice"}
|
||||
return bind(&v)
|
||||
}
|
||||
|
||||
func bind(v *flagVals) *flaggy.Parser {
|
||||
if v.workers == 0 {
|
||||
v.workers = 4
|
||||
}
|
||||
if v.query == "" {
|
||||
v.query = "in:inbox"
|
||||
}
|
||||
if v.srcs == "" {
|
||||
v.srcs = "onlyoffice"
|
||||
}
|
||||
p := cliparse.New("mail-sync")
|
||||
p.Description = "download mail to var/mail"
|
||||
p.String(&v.env, "", "env", ".env file")
|
||||
p.String(&v.out, "", "out", "var/mail root")
|
||||
p.Int(&v.workers, "", "workers", "concurrent downloads")
|
||||
p.Int(&v.limit, "", "limit", "max messages per source (0 = all)")
|
||||
p.Int(&v.offset, "", "offset", "skip first N messages per source")
|
||||
p.Bool(&v.force, "", "force", "overwrite existing message.json")
|
||||
p.Bool(&v.dryRun, "", "dry-run", "list counts without writing")
|
||||
p.String(&v.query, "", "query", "Gmail search query")
|
||||
p.String(&v.srcs, "", "source", "comma list: onlyoffice,gmail")
|
||||
return p
|
||||
}
|
||||
|
||||
// ParseCLI reads args into a CLIConfig. Exit codes: 0 ok, 2 usage.
|
||||
func ParseCLI(args []string) (CLIConfig, int, error) {
|
||||
fs := flag.NewFlagSet("mail/sync", flag.ContinueOnError)
|
||||
var (
|
||||
env = fs.String("env", "", ".env file (default: <cwd>/.env)")
|
||||
out = fs.String("out", "", "var/mail root (default: <cwd>/var/mail)")
|
||||
workers = fs.Int("workers", 4, "concurrent downloads")
|
||||
limit = fs.Int("limit", 0, "max messages per source (0 = all)")
|
||||
offset = fs.Int("offset", 0, "skip first N messages per source")
|
||||
force = fs.Bool("force", false, "overwrite existing message.json + attachments")
|
||||
dryRun = fs.Bool("dry-run", false, "list message counts without writing")
|
||||
query = fs.String("query", "in:inbox", "Gmail search query (gmail source only)")
|
||||
srcs = fs.String("source", "onlyoffice", "comma list: onlyoffice,gmail (default onlyoffice)")
|
||||
help = fs.Bool("help", false, "usage")
|
||||
)
|
||||
fs.SetOutput(os.Stderr)
|
||||
if err := fs.Parse(args); err != nil {
|
||||
v := flagVals{workers: 4, query: "in:inbox", srcs: "onlyoffice"}
|
||||
p := bind(&v)
|
||||
if err := cliparse.Parse(p, args); err != nil {
|
||||
if errors.Is(err, cliparse.ErrHelp) {
|
||||
return CLIConfig{Help: true}, 0, nil
|
||||
}
|
||||
return CLIConfig{}, 2, err
|
||||
}
|
||||
if *help || fs.NArg() > 0 {
|
||||
if len(p.TrailingArguments) > 0 {
|
||||
return CLIConfig{Help: true}, 0, nil
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return CLIConfig{}, 2, err
|
||||
}
|
||||
if *env == "" {
|
||||
*env = filepath.Join(wd, ".env")
|
||||
if v.env == "" {
|
||||
v.env = filepath.Join(wd, ".env")
|
||||
}
|
||||
if *out == "" {
|
||||
*out = filepath.Join(wd, "var", "mail")
|
||||
if v.out == "" {
|
||||
v.out = filepath.Join(wd, "var", "mail")
|
||||
}
|
||||
envVars := readEnv(*env)
|
||||
envVars := readEnv(v.env)
|
||||
cfg := SyncConfig{
|
||||
Out: *out,
|
||||
Workers: *workers,
|
||||
Limit: *limit,
|
||||
Offset: *offset,
|
||||
Force: *force,
|
||||
DryRun: *dryRun,
|
||||
Query: *query,
|
||||
Out: v.out,
|
||||
Workers: v.workers,
|
||||
Limit: v.limit,
|
||||
Offset: v.offset,
|
||||
Force: v.force,
|
||||
DryRun: v.dryRun,
|
||||
Query: v.query,
|
||||
Policy: RetryPolicy{},
|
||||
}
|
||||
cli := CLIConfig{Sync: cfg, Env: *env, Sources: *srcs}
|
||||
for _, s := range strings.Split(*srcs, ",") {
|
||||
out := CLIConfig{Sync: cfg, Env: v.env, Sources: v.srcs}
|
||||
for _, s := range strings.Split(v.srcs, ",") {
|
||||
switch strings.TrimSpace(s) {
|
||||
case "onlyoffice":
|
||||
u := pick(envVars["ONLYOFFICE_URL"], envVars["OO_URL"])
|
||||
user := pick(envVars["ONLYOFFICE_USER"], envVars["OO_USER"])
|
||||
pass := pick(envVars["ONLYOFFICE_PASS"], envVars["OO_PASSWORD"])
|
||||
if u == "" || user == "" || pass == "" {
|
||||
return CLIConfig{}, 2, fmt.Errorf("onlyoffice source needs ONLYOFFICE_URL/USER/PASS in %s", *env)
|
||||
return CLIConfig{}, 2, fmt.Errorf("onlyoffice source needs ONLYOFFICE_URL/USER/PASS in %s", v.env)
|
||||
}
|
||||
cfg.OO = &OOConfig{URL: u, User: user, Password: pass}
|
||||
case "gmail":
|
||||
@@ -85,30 +114,30 @@ func ParseCLI(args []string) (CLIConfig, int, error) {
|
||||
return CLIConfig{}, 2, fmt.Errorf("unknown source %q", s)
|
||||
}
|
||||
}
|
||||
cli.Sync = cfg
|
||||
return cli, 0, nil
|
||||
out.Sync = cfg
|
||||
return out, 0, nil
|
||||
}
|
||||
|
||||
// Main is the CLI entry: returns process exit code.
|
||||
func Main(args []string) int {
|
||||
cli, code, err := ParseCLI(args)
|
||||
cfg, code, err := ParseCLI(args)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "mail/sync:", err)
|
||||
return code
|
||||
}
|
||||
if cli.Help {
|
||||
if cfg.Help {
|
||||
fmt.Fprintln(os.Stderr, "usage: bin/mail/sync.go [--source onlyoffice,gmail] [--query GMAIL_Q] [--limit N] [--offset N] [--workers N] [--force] [--dry-run]")
|
||||
return 0
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 6*time.Hour)
|
||||
defer cancel()
|
||||
start := time.Now()
|
||||
stats, err := Run(ctx, cli.Sync)
|
||||
stats, err := Run(ctx, cfg.Sync)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "mail/sync:", err)
|
||||
return 1
|
||||
}
|
||||
if cli.Sync.DryRun {
|
||||
if cfg.Sync.DryRun {
|
||||
fmt.Printf("mail/sync: dry-run checked=%d (no writes)\n", stats.Checked)
|
||||
return 0
|
||||
}
|
||||
@@ -135,7 +164,6 @@ func readEnv(path string) map[string]string {
|
||||
k, v, _ := strings.Cut(line, "=")
|
||||
out[strings.TrimSpace(k)] = strings.Trim(strings.TrimSpace(v), "\"'")
|
||||
}
|
||||
// env overrides file
|
||||
for _, kv := range os.Environ() {
|
||||
k, v, ok := strings.Cut(kv, "=")
|
||||
if !ok {
|
||||
|
||||
+7
-22
@@ -15,6 +15,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
cliparse "github.com/eSlider/2dph/internal/cli"
|
||||
"github.com/eSlider/2dph/internal/mdleaves"
|
||||
)
|
||||
|
||||
@@ -23,29 +24,13 @@ func main() {
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
jsonOut := false
|
||||
files := ""
|
||||
root := "."
|
||||
for i := 0; i < len(args); i++ {
|
||||
a := args[i]
|
||||
switch {
|
||||
case a == "--json":
|
||||
jsonOut = true
|
||||
case a == "--files" && i+1 < len(args):
|
||||
i++
|
||||
files = args[i]
|
||||
case strings.HasPrefix(a, "--files="):
|
||||
files = strings.TrimPrefix(a, "--files=")
|
||||
case a == "-h" || a == "--help":
|
||||
fmt.Fprintln(os.Stderr, "bin/markdown/import.go [dir] [--files a.md,b.md] [--json]")
|
||||
return 0
|
||||
case strings.HasPrefix(a, "-"):
|
||||
fmt.Fprintln(os.Stderr, "unknown arg:", a)
|
||||
return 2
|
||||
default:
|
||||
root = a
|
||||
}
|
||||
c, err := mdleaves.ParseArgs(args)
|
||||
if err != nil {
|
||||
return cliparse.Fail(err)
|
||||
}
|
||||
jsonOut := c.JSONOut
|
||||
files := c.Files
|
||||
root := c.Root
|
||||
|
||||
var paths []string
|
||||
if files != "" {
|
||||
|
||||
+5
-17
@@ -16,8 +16,8 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
cliparse "github.com/eSlider/2dph/internal/cli"
|
||||
"github.com/eSlider/2dph/internal/duckstats"
|
||||
)
|
||||
|
||||
@@ -26,23 +26,11 @@ func main() {
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
jsonl := ""
|
||||
for i := 0; i < len(args); i++ {
|
||||
a := args[i]
|
||||
switch {
|
||||
case a == "--jsonl" && i+1 < len(args):
|
||||
i++
|
||||
jsonl = args[i]
|
||||
case strings.HasPrefix(a, "--jsonl="):
|
||||
jsonl = strings.TrimPrefix(a, "--jsonl=")
|
||||
case a == "-h" || a == "--help":
|
||||
fmt.Fprintln(os.Stderr, "bin/qa/stats.go [--jsonl FILE] # stdin = JSON [float,…]")
|
||||
return 0
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "unknown arg:", a)
|
||||
return 2
|
||||
}
|
||||
c, err := cliparse.ParseQAStats(args)
|
||||
if err != nil {
|
||||
return cliparse.Fail(err)
|
||||
}
|
||||
jsonl := c.JSONL
|
||||
if jsonl != "" {
|
||||
n, err := duckstats.CountJSONL(jsonl)
|
||||
if err != nil {
|
||||
|
||||
+7
-36
@@ -15,8 +15,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
cliparse "github.com/eSlider/2dph/internal/cli"
|
||||
"github.com/eSlider/2dph/internal/duckstats"
|
||||
"github.com/eSlider/2dph/internal/reasoner"
|
||||
)
|
||||
@@ -26,42 +26,13 @@ func main() {
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
base := os.Getenv("REASONER_BASE_URL")
|
||||
if base == "" {
|
||||
base = "http://127.0.0.1:11435/v1"
|
||||
c, err := reasoner.ParseArgs(args)
|
||||
if err != nil {
|
||||
return cliparse.Fail(err)
|
||||
}
|
||||
model := os.Getenv("REASONER_MODEL")
|
||||
if model == "" {
|
||||
model = reasoner.OllamaRAM
|
||||
}
|
||||
jsonOut := false
|
||||
device := "cpu"
|
||||
for i := 0; i < len(args); i++ {
|
||||
a := args[i]
|
||||
switch {
|
||||
case a == "--json":
|
||||
jsonOut = true
|
||||
case a == "--model" && i+1 < len(args):
|
||||
i++
|
||||
model = args[i]
|
||||
case strings.HasPrefix(a, "--model="):
|
||||
model = strings.TrimPrefix(a, "--model=")
|
||||
case a == "--base-url" && i+1 < len(args):
|
||||
i++
|
||||
base = args[i]
|
||||
case a == "--device" && i+1 < len(args):
|
||||
i++
|
||||
device = args[i]
|
||||
case a == "-h" || a == "--help":
|
||||
fmt.Fprintln(os.Stderr, "bin/reasoner/bakeoff.go [--model ID] [--base-url URL] [--device cpu] [--json]")
|
||||
return 0
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "unknown arg:", a)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
c := reasoner.Client{BaseURL: base, Model: model, Device: device}
|
||||
rep := reasoner.Run(c)
|
||||
base, model, jsonOut, device := c.Base, c.Model, c.JSONOut, c.Device
|
||||
client := reasoner.Client{BaseURL: base, Model: model, Device: device}
|
||||
rep := reasoner.Run(client)
|
||||
lat := make([]float64, 0, len(rep.Prompts))
|
||||
for _, p := range rep.Prompts {
|
||||
lat = append(lat, float64(p.LatencyMS))
|
||||
|
||||
@@ -143,6 +143,22 @@ class BinLayoutTest(unittest.TestCase):
|
||||
shebang = (ROOT / "bin" / "facts" / "audit.go").read_text()
|
||||
self.assertIn("contradict", shebang)
|
||||
|
||||
def test_d23_flaggy_cli(self) -> None:
|
||||
self.assertTrue((ROOT / "internal" / "cli" / "cli.go").is_file())
|
||||
self.assertIn("github.com/integrii/flaggy", (ROOT / "go.mod").read_text())
|
||||
plan = (ROOT / "PLAN.md").read_text()
|
||||
self.assertIn("D23", plan)
|
||||
self.assertIn("flaggy", plan)
|
||||
complete = (ROOT / "bin" / "cli" / "complete.go").read_text()
|
||||
first = complete.splitlines()[0]
|
||||
self.assertTrue(first.startswith("//usr/bin/env go run"), first)
|
||||
self.assertIn("complete.go bash", complete)
|
||||
self.assertIn("brain-search", complete)
|
||||
chats_import = (ROOT / "internal" / "chats" / "import.go").read_text()
|
||||
self.assertNotIn("flag.NewFlagSet", chats_import)
|
||||
args = (ROOT / "internal" / "brain" / "rank" / "args.go").read_text()
|
||||
self.assertIn("internal/cli", args)
|
||||
|
||||
def test_mail_import_is_shebang_not_brain_write(self) -> None:
|
||||
self._assert_shebang("bin/mail/import.go")
|
||||
index_mail = (ROOT / "bin" / "mail" / "index_mail").read_text()
|
||||
|
||||
+9
-71
@@ -16,9 +16,9 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/eSlider/2dph/internal/cli"
|
||||
"github.com/eSlider/2dph/internal/websearch"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@@ -28,77 +28,15 @@ func main() {
|
||||
}
|
||||
|
||||
func run(args []string) int {
|
||||
var (
|
||||
query, site, lang, fresh, category, engines string
|
||||
limit = websearch.DefaultLimit
|
||||
jsonOut, refresh, force bool
|
||||
ttl = float64(websearch.CacheTTL)
|
||||
timeout = 25
|
||||
)
|
||||
i := 0
|
||||
for i < len(args) {
|
||||
a := args[i]
|
||||
switch {
|
||||
case a == "--json":
|
||||
jsonOut = true
|
||||
case a == "--refresh":
|
||||
refresh = true
|
||||
case a == "--force":
|
||||
force = true
|
||||
case (a == "-n" || a == "--limit") && i+1 < len(args):
|
||||
i++
|
||||
n, err := strconv.Atoi(args[i])
|
||||
if err != nil || n < 0 {
|
||||
fmt.Fprintln(os.Stderr, "web/search: --limit must be a non-negative integer")
|
||||
return 2
|
||||
}
|
||||
limit = n
|
||||
case a == "--site" && i+1 < len(args):
|
||||
i++
|
||||
site = args[i]
|
||||
case a == "--lang" && i+1 < len(args):
|
||||
i++
|
||||
lang = args[i]
|
||||
case a == "--fresh" && i+1 < len(args):
|
||||
i++
|
||||
fresh = args[i]
|
||||
case a == "--category" && i+1 < len(args):
|
||||
i++
|
||||
category = args[i]
|
||||
case a == "--engines" && i+1 < len(args):
|
||||
i++
|
||||
engines = args[i]
|
||||
case a == "--ttl" && i+1 < len(args):
|
||||
i++
|
||||
v, err := strconv.ParseFloat(args[i], 64)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "web/search: --ttl must be a number")
|
||||
return 2
|
||||
}
|
||||
ttl = v
|
||||
case a == "--timeout" && i+1 < len(args):
|
||||
i++
|
||||
n, err := strconv.Atoi(args[i])
|
||||
if err != nil || n <= 0 {
|
||||
fmt.Fprintln(os.Stderr, "web/search: --timeout must be a positive integer")
|
||||
return 2
|
||||
}
|
||||
timeout = n
|
||||
case a == "-h" || a == "--help":
|
||||
fmt.Fprintln(os.Stderr, `usage: bin/web/search.go QUERY [--json] [-n N] [--site HOST] [--lang LANG] [--fresh day|week|month|year] [--category CAT] [--engines LIST] [--refresh] [--force]`)
|
||||
return 0
|
||||
case len(a) > 0 && a[0] != '-' && query == "":
|
||||
query = a
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "web/search: unknown flag %s\n", a)
|
||||
return 2
|
||||
}
|
||||
i++
|
||||
}
|
||||
if query == "" {
|
||||
fmt.Fprintln(os.Stderr, "web/search: query required")
|
||||
return 2
|
||||
c, err := websearch.ParseArgs(args)
|
||||
if err != nil {
|
||||
return cli.Fail(err)
|
||||
}
|
||||
query, site, lang, fresh, category, engines := c.Query, c.Site, c.Lang, c.Fresh, c.Category, c.Engines
|
||||
limit := c.Limit
|
||||
jsonOut, refresh, force := c.JSONOut, c.Refresh, c.Force
|
||||
ttl := c.TTL
|
||||
timeout := c.Timeout
|
||||
if site != "" {
|
||||
query = "site:" + site + " " + query
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user