Skip to content

Commit

Permalink
Extract fzf args from map to arg for thread-last
Browse files Browse the repository at this point in the history
- To allow for threading (thread-last) of fzf input args, move the
  args from `in`-key in opt map to a single arg
- Validate args with spec
  • Loading branch information
joakimen committed Jul 7, 2023
1 parent 0e7a103 commit 5cbf172
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 49 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,30 @@ Add the following to your `bb.edn` or `deps.edn`:

#### Passing data to stdin

> :bulb: Since `v0.0.2`, input-arguments have been moved from the `in`-key in the options map to its own argument to facilitate threading.
To provide input arguments to fzf via STDIN instead of using the default file selection command, the arguments can be passed as a vector of strings.

```clojure
(fzf {:in ["one" "two" "three"]})
(fzf ["one" "two" "three"])
;; user selects an item
"two"
```

Threading

```clojure
(->> ["quick" "brown" "fox"] fzf) ;; => fox
```

Threading w/options

```clojure
(->> ["quick" "brown" "fox"]
(map upper-case)
(fzf {:multi true})) ;; => ["QUICK" "FOX"]
```

### Available options

[fzf](https://github.com/junegunn/fzf) supports a slew of options, so I have only included the ones I frequently use myself.
Expand Down
36 changes: 24 additions & 12 deletions src/fzf/core.clj
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
(ns fzf.core
"Public interface to the fzf-wrapper"
(:require [fzf.impl :as i]
[clojure.spec.alpha :as s]))
(:require [clojure.spec.alpha :as s]
[fzf.impl :as i]))

(defn fzf
"Public interface to fzf.
Options map (all keys are optional):
- in: Vec of args passed as input to fzf as stdin
`opts`: Options map (all keys are optional)
- dir: String indicating the startup-dir of the fzf-command
- multi: Bool, toggles multi-select in fzf. If true, fzf returns a vector instead of string
- preview: String, preview-command for the currently selected item
Expand All @@ -20,20 +19,33 @@
- tac: Bool, reverse the order of the input
- case-insensitive: Bool, toggle case-insensitive search (default: smart-case)
- exact: Bool, toggle exact search (default: fuzzy)
`args`: Input arguments to fzf (optional, list of strings)
Examples:
(fzf) ;; => \"myfile.txt\"
(fzf :multi true
:in [\"one\" \"two\" \"three\"]
:reverse true) ;; => [\"one\" \"two\"]
(->> [\"quick\" \"brown\" \"fox\"]
(map clojure.string/upper-case)
fzf) ;; => \"FOX\"
(fzf {:multi true
:reverse true}
[\"one \" \"two \" \"three \"]) ;; => [\"one\" \"two\"]
Returns:
- on success with :multi = false (default): the selected item as string
- on success with :multi = true: the selected item(s) as vector of strings
- on error or ctrl-c: nil"
([] (i/fzf))
([opts]
{:pre [(s/valid? :fzf/opts opts)]}
(i/fzf opts)))
([] (fzf {} []))
([opts-or-args]
(if (map? opts-or-args)
(fzf opts-or-args [])
(fzf {} opts-or-args)))
([opts args]
{:pre [(s/and (s/valid? :fzf/opts opts)
(s/valid? :fzf/args args))]}
(i/fzf opts args)))
59 changes: 29 additions & 30 deletions src/fzf/impl.clj
Original file line number Diff line number Diff line change
@@ -1,39 +1,38 @@
(ns fzf.impl
"Implementation of fzf-wrapper"
(:require [babashka.process :as p]
[babashka.fs :as fs]
(:require [babashka.fs :as fs]
[babashka.process :as p]
[clojure.string :as str]))

(defn parse-opts
"Parse fzf and process options"
([] (parse-opts {}))
([opts]
(let [{:keys [in dir multi preview tac case-insensitive exact reverse height]
{:keys [header-str header-lines header-first]} :header} opts]
{:cmd (cond-> ["fzf"]
multi (conj "--multi")
reverse (conj "--reverse")
tac (conj "--tac")
case-insensitive (conj "-i")
exact (conj "--exact")
preview (conj "--preview" preview)
header-str (conj "--header" header-str)
header-lines (conj "--header-lines" header-lines)
header-first (conj "--header-first")
height (conj "--height" height))
:opts (cond-> {:in :inherit
:out :string
:err :inherit}
in (assoc :in (str/join "\n" in))
dir (assoc :dir (-> dir fs/expand-home str)))})))
[opts args]
(let [{:keys [dir multi preview tac case-insensitive exact reverse height]
{:keys [header-str header-lines header-first]} :header} opts]
{:cmd (cond-> ["fzf"]
multi (conj "--multi")
reverse (conj "--reverse")
tac (conj "--tac")
case-insensitive (conj "-i")
exact (conj "--exact")
preview (conj "--preview" preview)
header-str (conj "--header" header-str)
header-lines (conj "--header-lines" header-lines)
header-first (conj "--header-first")
height (conj "--height" height))
:opts (cond-> {:in :inherit
:out :string
:err :inherit}
(not-empty args) (assoc :in (str/join "\n" args))
dir (assoc :dir (-> dir fs/expand-home str)))}))

(defn fzf
"Internal interface to fzf"
([] (fzf {}))
([opts] (let [multi (:multi opts)
{:keys [cmd opts]} (parse-opts opts)
{:keys [out exit]} @(p/process cmd opts)]
(if (zero? exit)
(cond-> (str/trim out)
multi str/split-lines)
nil))))
[opts args]
(let [multi (:multi opts)
{:keys [cmd opts]} (parse-opts opts args)
{:keys [out exit]} @(p/process cmd opts)]
(if (zero? exit)
(cond-> (str/trim out)
multi str/split-lines)
nil)))
16 changes: 10 additions & 6 deletions test/fzf/impl_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
(t/deftest parse-opts-test
(t/testing "no options produce expected defaults"
(t/is (= {:cmd ["fzf"], :opts {:in :inherit, :out :string, :err :inherit}}
(i/parse-opts))))
(i/parse-opts {} []))))
(t/testing "adding fzf flags produce correct command string"
(t/is (= ["fzf" "--multi" "--reverse" "--tac" "-i" "--exact" "--preview" "echo {}" "--header" "header-text" "--header-lines" 2 "--header-first" "--height" "10%"]
(:cmd (i/parse-opts {:multi true
Expand All @@ -17,14 +17,18 @@
:header {:header-str "header-text"
:header-lines 2
:header-first true}
:height "10%"})))))
:height "10%"}
[])))))
(t/testing "providing input-arguments maps them to process stdin"
(t/is (= {:in "one\ntwo\nthree", :out :string, :err :inherit}
(:opts (i/parse-opts {:in ["one" "two" "three"]}))))
(:opts (i/parse-opts {} ["one" "two" "three"]))))
(t/is (= {:in "1\n2\n3", :out :string, :err :inherit}
(:opts (i/parse-opts {:in [1 2 3]}))))
(:opts (i/parse-opts {} [1 2 3]))))
(t/is (= {:in ":one\n:two\n:three", :out :string, :err :inherit}
(:opts (i/parse-opts {:in [:one :two :three]})))))
(:opts (i/parse-opts {} [:one :two :three])))))
(t/testing "providing no input-arguments should retain default proc options"
(t/is (= {:in :inherit, :out :string, :err :inherit}
(:opts (i/parse-opts {} [])))))
(t/testing "option for startup-dir to correctly mapped to process options"
(t/is (= {:in :inherit, :out :string, :err :inherit, :dir "/tmp"}
(:opts (i/parse-opts {:dir "/tmp"}))))))
(:opts (i/parse-opts {:dir "/tmp"} []))))))

0 comments on commit 5cbf172

Please sign in to comment.