mirror of
https://github.com/coalaura/ffwebp.git
synced 2025-12-02 18:22:53 +00:00
patterns and help topics
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
_ "github.com/coalaura/ffwebp/internal/builtins"
|
||||
"github.com/coalaura/ffwebp/internal/codec"
|
||||
"github.com/coalaura/ffwebp/internal/effects"
|
||||
"github.com/coalaura/ffwebp/internal/help"
|
||||
"github.com/coalaura/ffwebp/internal/logx"
|
||||
"github.com/coalaura/ffwebp/internal/opts"
|
||||
"github.com/nfnt/resize"
|
||||
@@ -33,7 +34,7 @@ func main() {
|
||||
&cli.StringFlag{
|
||||
Name: "output",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "output file, directory, or pattern (\"-\" = stdout)",
|
||||
Usage: "output file, directory, or pattern (\"-\" = stdout, supports %d and templates)",
|
||||
Value: "-",
|
||||
},
|
||||
&cli.IntFlag{
|
||||
@@ -104,6 +105,9 @@ func main() {
|
||||
EnableShellCompletion: true,
|
||||
UseShortOptionHandling: true,
|
||||
Suggest: true,
|
||||
Commands: []*cli.Command{
|
||||
help.Command(),
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(context.Background(), os.Args); err != nil {
|
||||
@@ -164,11 +168,29 @@ func run(_ context.Context, cmd *cli.Command) error {
|
||||
var outputs []string
|
||||
|
||||
if len(inputs) == 1 {
|
||||
outputs = []string{output}
|
||||
out := output
|
||||
|
||||
if out != "-" {
|
||||
if hasTemplate(out) {
|
||||
out = formatTemplate(out, inputs[0], 0, startNum)
|
||||
} else if hasSeq(out) {
|
||||
out = formatSeq(out, 0, startNum)
|
||||
}
|
||||
}
|
||||
|
||||
outputs = []string{out}
|
||||
} else {
|
||||
switch {
|
||||
case output == "-":
|
||||
return fmt.Errorf("multiple inputs require an output pattern or directory, not '-' ")
|
||||
case hasTemplate(output):
|
||||
outs := make([]string, len(inputs))
|
||||
|
||||
for i := range inputs {
|
||||
outs[i] = formatTemplate(output, inputs[i], i, startNum)
|
||||
}
|
||||
|
||||
outputs = outs
|
||||
case hasSeq(output):
|
||||
outs := make([]string, len(inputs))
|
||||
|
||||
@@ -196,7 +218,7 @@ func run(_ context.Context, cmd *cli.Command) error {
|
||||
outs := make([]string, len(inputs))
|
||||
|
||||
for i := range inputs {
|
||||
outs[i] = output
|
||||
outs[i] = outDir
|
||||
}
|
||||
|
||||
outputs = outs
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func hasSeq(s string) bool {
|
||||
@@ -161,3 +162,49 @@ func expandSeq(pat string) ([]string, error) {
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func hasTemplate(s string) bool {
|
||||
if !strings.Contains(s, "{") {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(s, "{/.}") ||
|
||||
strings.Contains(s, "{/}") ||
|
||||
strings.Contains(s, "{.}") ||
|
||||
strings.Contains(s, "{}") ||
|
||||
strings.Contains(s, "{//}") ||
|
||||
strings.Contains(s, "{#}")
|
||||
}
|
||||
|
||||
func trimExt(p string) string {
|
||||
return strings.TrimSuffix(p, filepath.Ext(p))
|
||||
}
|
||||
|
||||
func formatTemplate(pattern, in string, idx, startNum int) string {
|
||||
if hasSeq(pattern) {
|
||||
pattern = formatSeq(pattern, idx, startNum)
|
||||
}
|
||||
|
||||
dir := filepath.Dir(in)
|
||||
base := filepath.Base(in)
|
||||
|
||||
// Special-case "-" (stdin)
|
||||
if in == "-" {
|
||||
dir = "."
|
||||
base = "stdin"
|
||||
}
|
||||
|
||||
fullNoExt := trimExt(in)
|
||||
baseNoExt := trimExt(base)
|
||||
n := idx + startNum - 1
|
||||
|
||||
r := strings.NewReplacer(
|
||||
"{/.}", baseNoExt,
|
||||
"{//}", dir,
|
||||
"{/}", base,
|
||||
"{.}", fullNoExt,
|
||||
"{}", in,
|
||||
"{#}", strconv.Itoa(n),
|
||||
)
|
||||
|
||||
return r.Replace(pattern)
|
||||
}
|
||||
|
||||
93
internal/help/help.go
Normal file
93
internal/help/help.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package help
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
type HelpTopic struct {
|
||||
Name string
|
||||
Description string
|
||||
Content []byte
|
||||
}
|
||||
|
||||
var (
|
||||
//go:embed topics/patterns.txt
|
||||
topicPatterns []byte
|
||||
|
||||
topics = []HelpTopic{
|
||||
{"topics", "List available help topics", nil},
|
||||
{"patterns", "Input/output path patterns: globs, %d sequences, templates", topicPatterns},
|
||||
}
|
||||
)
|
||||
|
||||
func Command() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "help",
|
||||
Usage: "Show help or a specific topic",
|
||||
ArgsUsage: "[topic]",
|
||||
Action: func(ctx context.Context, c *cli.Command) error {
|
||||
args := c.Args().Slice()
|
||||
|
||||
if len(args) == 0 {
|
||||
parent := c.Root()
|
||||
|
||||
if parent == nil {
|
||||
parent = c
|
||||
}
|
||||
|
||||
return cli.ShowAppHelp(parent)
|
||||
}
|
||||
|
||||
topic := strings.ToLower(strings.TrimSpace(args[0]))
|
||||
|
||||
switch topic {
|
||||
case "topics":
|
||||
return printTopics(c.Writer)
|
||||
default:
|
||||
return printTopic(c.Writer, topic)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func printTopics(w io.Writer) error {
|
||||
var length int
|
||||
|
||||
for _, topic := range topics {
|
||||
length = max(length, len(topic.Name))
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, "USAGE:")
|
||||
fmt.Fprintln(w, " ffwebp help [topic]")
|
||||
fmt.Fprintln(w)
|
||||
fmt.Fprintln(w, "TOPICS:")
|
||||
|
||||
for _, topic := range topics {
|
||||
fmt.Fprintf(w, " %-*s - %s\n", length, topic.Name, topic.Description)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTopic(w io.Writer, name string) error {
|
||||
var topic *HelpTopic
|
||||
|
||||
for _, tp := range topics {
|
||||
if tp.Name == name {
|
||||
topic = &tp
|
||||
}
|
||||
}
|
||||
|
||||
if topic == nil {
|
||||
return fmt.Errorf("unknown help topic: %q (see `ffwebp help topics`)", name)
|
||||
}
|
||||
|
||||
_, err := w.Write(topic.Content)
|
||||
return err
|
||||
}
|
||||
28
internal/help/topics/patterns.txt
Normal file
28
internal/help/topics/patterns.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
USAGE:
|
||||
ffwebp [global options]
|
||||
|
||||
TOPIC:
|
||||
Path patterns for input (-i) and output (-o)
|
||||
|
||||
INPUT PATTERNS:
|
||||
- Globs: *, ?, [..] (shell-expanded on Unix; expanded by ffwebp on Windows)
|
||||
- Numeric sequences: %d, %02d (start at --start-number, default 1)
|
||||
|
||||
OUTPUT PATTERNS:
|
||||
- %d sequences (same numbering as input/index)
|
||||
- Templates (GNU-parallel style):
|
||||
{} full input path
|
||||
{.} input path without last extension
|
||||
{/} basename (file name)
|
||||
{/.} basename without extension
|
||||
{//} directory (no trailing slash)
|
||||
{#} sequence number (uses --start-number)
|
||||
|
||||
EXAMPLES:
|
||||
ffwebp -i "*.png" -o "{/.}.webp"
|
||||
ffwebp -i "frames/%03d.png" -o "out/frame-{#}.png" --start-number 10
|
||||
ffwebp -i "images/**/*.jpg" -o "out/{//}/{/.}.avif"
|
||||
|
||||
NOTES:
|
||||
- Output codec is inferred from the final extension unless --codec is set.
|
||||
- With multiple inputs, output must be a pattern or directory.
|
||||
Reference in New Issue
Block a user