From 5d4dadc02b57ccc3f2cef302cc04758d513aafb0 Mon Sep 17 00:00:00 2001
From: DTTerastar
Date: Sun, 5 Apr 2026 15:47:31 -0400
Subject: [PATCH] Add bodyweights commands and --exercse flag
---
.github/workflows/release-drafter.yml | 3 --
cmd/workouts.go | 46 +++++++++++++++++++++++++++
2 files changed, 46 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index faa777a..6ddc541 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -1,9 +1,6 @@
name: Release Drafter
on:
- push:
- branches:
- - main
pull_request:
types: [opened, reopened, synchronize]
diff --git a/cmd/workouts.go b/cmd/workouts.go
index 45fc340..ec481ab 100644
--- a/cmd/workouts.go
+++ b/cmd/workouts.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
+ "strings"
"time"
"github.com/dturner/liftoff-cli/internal/client"
@@ -48,6 +49,7 @@ type Post struct {
var listJSONFlag bool
var listSinceFlag string
+var listExerciseFlag string
var listCmd = &cobra.Command{
Use: "list",
@@ -73,6 +75,9 @@ var listCmd = &cobra.Command{
}
posts = filtered
}
+ if listExerciseFlag != "" {
+ posts = filterExercises(posts, listExerciseFlag)
+ }
if listJSONFlag {
return printJSON(posts)
}
@@ -127,6 +132,47 @@ func init() {
workoutsCmd.AddCommand(showCmd)
listCmd.Flags().BoolVar(&listJSONFlag, "json", false, "Output as JSON instead of fitdown")
listCmd.Flags().StringVar(&listSinceFlag, "since", "", "Filter workouts on or after date (e.g. 2025-01-01, 30d, 4w, 6m, 1y)")
+ listCmd.Flags().StringVar(&listExerciseFlag, "exercise", "", "Filter to exercises matching this name (word-prefix match)")
+}
+
+// matchesExercise checks if every word in pattern matches a word-prefix in name.
+// e.g. "pull up" matches "Assisted Pull Ups", "chin" matches "Assisted Chin Ups"
+// but "chin" does not match "Machine Row".
+func matchesExercise(name, pattern string) bool {
+ nameWords := strings.Fields(strings.ToLower(name))
+ patWords := strings.Fields(strings.ToLower(pattern))
+ for _, pw := range patWords {
+ found := false
+ for _, nw := range nameWords {
+ if strings.HasPrefix(nw, pw) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
+ }
+ }
+ return true
+}
+
+// filterExercises keeps only exercises matching pattern within each post,
+// and drops posts that end up with no matching exercises.
+func filterExercises(posts []Post, pattern string) []Post {
+ var out []Post
+ for _, p := range posts {
+ var matched []ExerciseData
+ for _, e := range p.ExerciseData {
+ if matchesExercise(e.ExerciseName, pattern) {
+ matched = append(matched, e)
+ }
+ }
+ if len(matched) > 0 {
+ p.ExerciseData = matched
+ out = append(out, p)
+ }
+ }
+ return out
}
func printFitdown(posts []Post) error {