Skip to content

Commit

Permalink
bbin ls now prints table, edn via flag --edn
Browse files Browse the repository at this point in the history
  • Loading branch information
eval committed Feb 23, 2023
1 parent ac103ac commit beaa047
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 5 deletions.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ $ bbin install https://gist.githubusercontent.com/rads/da8ecbce63fe305f352063781
{:coords {:bbin/url "https://gist.githubusercontent.com/rads/da8ecbce63fe305f3520637810ff9506/raw/25e47ce2fb5f9a7f9d12a20423e801b64c20e787/portal.clj"}}
# Open a Portal window with all installed scripts
$ portal <(bbin ls)
$ portal <(bbin ls --edn)
```

📦 See the [**Scripts and Projects**](https://github.com/babashka/bbin/wiki/Scripts-and-Projects) wiki page for a list of CLI tools from the community. This list is just a starting point — any existing Babashka script or project can be installed out-of-the-box!
Expand Down Expand Up @@ -96,6 +96,7 @@ $ bbin uninstall watch
# Show installed scripts
$ bbin ls
$ bbin ls --edn
# Show the bin path
$ bbin bin
Expand Down Expand Up @@ -181,6 +182,19 @@ If no `--git/tag` or `--git/sha` is provided, the latest tag from the Git repo w

**List installed scripts**

- By default, this shows all installed scripts in a human readable table.
- When piping this output to another program, e.g. `bbin ls | wc -l`, the table header is omitted, git shas are shown in full and no escape characters are used. Flag `--plain` shows what this looks like.


**Supported Options:**

- `--edn`
- Format as edn.
- `--no-color`
- Ensure the output contains no escape characters (setting environment variable `NO_COLOR` has the same effect).
- `--plain`
- Show what the table looks like when piped to another program, e.g. what `wc` sees when doing `bbin ls | wc -l`.

---

### `bbin bin`
Expand Down
105 changes: 103 additions & 2 deletions bbin
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
[taoensso.timbre :as log]
[clojure.edn :as edn]))

(defn is-tty
[fd key]
(-> ["test" "-t" (str fd)]
(p/process {key :inherit :env {}})
deref
:exit
(= 0)))

(defn user-home []
(System/getProperty "user.home"))

Expand All @@ -55,6 +63,79 @@
(binding [*print-namespace-maps* false]
(pprint/pprint x)))

(defn print-table
"Print table to stdout.
Examples:
;; extract columns from rows
(print-table [{:a \"one\" :b \"two\"}])
a b
─── ───
one two
;; provide columns (as b is an empty column, it will be skipped)
(print-table [:a :b] [{:a \"one\" :b nil}])
a
───
one
;; ensure all columns being shown:
(print-table [:a :b] [{:a \"one\"}] {:show-empty-columns true})
;; provide columns with labels and apply column coercion
(print-table {:a \"option A\" :b \"option B\"} [{:a \"one\" :b nil}]
{:column-coercions {:b (fnil boolean false)}})
option A option B
──────── ────────
one false
Options:
- `column-coercions` (`{}`) fn that given a key `k` yields an fn to be applied to every `(k row)` *iff* row contains key `k`.
See example above.
- `skip-header` (`false`) don't print column names and divider (typically use this when stdout is no tty).
- `show-empty-columns` (`false`) print every column, even if it results in empty columns.
- `no-color` (`false`) prevent printing escape characters to stdout."
([rows] (print-table (keys (first rows)) rows nil))
([ks rows] (print-table ks rows nil))
([ks rows {:keys [show-empty-columns skip-header no-color column-coercions]
:or {show-empty-columns false skip-header false no-color false column-coercions {}}}]
(let [wrap-bold (fn [s] (if no-color s (str "\033[1m" s "\033[0m")))
row-get (fn [row k]
(when (contains? row k)
((column-coercions k identity) (k row))))
key->label (if (map? ks) ks #(subs (str (keyword %)) 1))
header-keys (if (map? ks) (keys ks) ks)
;; ensure all header-keys exist for every row and every value is a string
rows (map (fn [row]
(reduce (fn [acc k]
(assoc acc k (str (row-get row k)))) {} header-keys)) rows)
header-keys (if show-empty-columns
header-keys
(let [non-empty-cols (remove
(fn [k] (every? str/blank? (map k rows)))
header-keys)]
(filter (set non-empty-cols) header-keys)))
header-labels (map key->label header-keys)
column-widths (reduce (fn [acc k]
(let [val-widths (map count (cons (key->label k) (map k rows)))]
(assoc acc k (apply max val-widths)))) {} header-keys)
row-fmt (str/join " " (map #(str "%-" (column-widths %) "s") header-keys))
cells->formatted-row #(apply format row-fmt %)
header-row (wrap-bold
(cells->formatted-row header-labels))
div-row (wrap-bold
(cells->formatted-row
(map (fn [k]
(apply str (take (column-widths k) (repeat \u2500)))) header-keys)))
data-rows (map #(cells->formatted-row (map % header-keys)) rows)]
(when (seq header-keys)
(let [header (if skip-header (vector) (vector header-row div-row))]
(println (apply str (interpose \newline (into header data-rows)))))))))

(defn print-help [& _]
(println (str/trim "
Usage: bbin <command>
Expand Down Expand Up @@ -725,9 +806,29 @@ WARNING: - Set the BABASHKA_BBIN_BIN_DIR env variable to \"$HOME/.babashka/bbi
(filter second)
(into {})))

(defn- printable-scripts [scripts]
(map (fn [[bin {:as m coords :coords lib :lib}]]
(cond-> (assoc {} :bin bin)
lib (assoc :lib lib)
coords (merge coords)))
scripts))

(defn- print-scripts [printable-scripts {:as cli-opts :keys [no-color plain]}]
(let [piping? (not (util/is-tty 1 :out))
skip-header? (or plain piping?)
no-color? (or plain no-color piping?
(System/getenv "NO_COLOR") (= "dumb" (System/getenv "TERM")))
column-atts '(:bin :lib :bbin/url :local/root :git/url :git/tag :git/sha)
column-coercions {:git/sha #(if (or plain piping?) % (subs % 0 7))}]
(util/print-table column-atts (sort-by :bin printable-scripts) {:skip-header skip-header?
:column-coercions column-coercions
:no-color no-color?})))

(defn ls [cli-opts]
(-> (load-scripts cli-opts)
(util/pprint cli-opts)))
(let [scripts (load-scripts cli-opts)]
(if (:edn cli-opts)
(util/pprint scripts cli-opts)
(print-scripts (printable-scripts scripts) cli-opts))))

(defn bin [cli-opts]
(println (str (util/bin-dir cli-opts))))
Expand Down
24 changes: 22 additions & 2 deletions src/babashka/bbin/scripts.clj
Original file line number Diff line number Diff line change
Expand Up @@ -544,9 +544,29 @@
(filter second)
(into {})))

(defn- printable-scripts [scripts]
(map (fn [[bin {coords :coords lib :lib}]]
(cond-> (assoc {} :bin bin)
lib (assoc :lib lib)
coords (merge coords)))
scripts))

(defn- print-scripts [printable-scripts {:as _cli-opts :keys [no-color plain]}]
(let [piping? (not (util/is-tty 1 :out))
skip-header? (or plain piping?)
no-color? (or plain no-color piping?
(System/getenv "NO_COLOR") (= "dumb" (System/getenv "TERM")))
column-atts '(:bin :lib :bbin/url :local/root :git/url :git/tag :git/sha)
column-coercions {:git/sha #(if (or plain piping?) % (subs % 0 7))}]
(util/print-table column-atts (sort-by :bin printable-scripts) {:skip-header skip-header?
:column-coercions column-coercions
:no-color no-color?})))

(defn ls [cli-opts]
(-> (load-scripts cli-opts)
(util/pprint cli-opts)))
(let [scripts (load-scripts cli-opts)]
(if (:edn cli-opts)
(util/pprint scripts cli-opts)
(print-scripts (printable-scripts scripts) cli-opts))))

(defn bin [cli-opts]
(println (str (util/bin-dir cli-opts))))
Expand Down
81 changes: 81 additions & 0 deletions src/babashka/bbin/util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@
[taoensso.timbre :as log]
[clojure.edn :as edn]))

(defn is-tty
[fd key]
(-> ["test" "-t" (str fd)]
(p/process {key :inherit :env {}})
deref
:exit
(= 0)))

(defn user-home []
(System/getProperty "user.home"))

Expand All @@ -21,6 +29,79 @@
(binding [*print-namespace-maps* false]
(pprint/pprint x)))

(defn print-table
"Print table to stdout.
Examples:
;; extract columns from rows
(print-table [{:a \"one\" :b \"two\"}])
a b
─── ───
one two
;; provide columns (as b is an empty column, it will be skipped)
(print-table [:a :b] [{:a \"one\" :b nil}])
a
───
one
;; ensure all columns being shown:
(print-table [:a :b] [{:a \"one\"}] {:show-empty-columns true})
;; provide columns with labels and apply column coercion
(print-table {:a \"option A\" :b \"option B\"} [{:a \"one\" :b nil}]
{:column-coercions {:b (fnil boolean false)}})
option A option B
──────── ────────
one false
Options:
- `column-coercions` (`{}`) fn that given a key `k` yields an fn to be applied to every `(k row)` *iff* row contains key `k`.
See example above.
- `skip-header` (`false`) don't print column names and divider (typically use this when stdout is no tty).
- `show-empty-columns` (`false`) print every column, even if it results in empty columns.
- `no-color` (`false`) prevent printing escape characters to stdout."
([rows] (print-table (keys (first rows)) rows nil))
([ks rows] (print-table ks rows nil))
([ks rows {:keys [show-empty-columns skip-header no-color column-coercions]
:or {show-empty-columns false skip-header false no-color false column-coercions {}}}]
(let [wrap-bold (fn [s] (if no-color s (str "\033[1m" s "\033[0m")))
row-get (fn [row k]
(when (contains? row k)
((column-coercions k identity) (k row))))
key->label (if (map? ks) ks #(subs (str (keyword %)) 1))
header-keys (if (map? ks) (keys ks) ks)
;; ensure all header-keys exist for every row and every value is a string
rows (map (fn [row]
(reduce (fn [acc k]
(assoc acc k (str (row-get row k)))) {} header-keys)) rows)
header-keys (if show-empty-columns
header-keys
(let [non-empty-cols (remove
(fn [k] (every? str/blank? (map k rows)))
header-keys)]
(filter (set non-empty-cols) header-keys)))
header-labels (map key->label header-keys)
column-widths (reduce (fn [acc k]
(let [val-widths (map count (cons (key->label k) (map k rows)))]
(assoc acc k (apply max val-widths)))) {} header-keys)
row-fmt (str/join " " (map #(str "%-" (column-widths %) "s") header-keys))
cells->formatted-row #(apply format row-fmt %)
header-row (wrap-bold
(cells->formatted-row header-labels))
div-row (wrap-bold
(cells->formatted-row
(map (fn [k]
(apply str (take (column-widths k) (repeat \u2500)))) header-keys)))
data-rows (map #(cells->formatted-row (map % header-keys)) rows)]
(when (seq header-keys)
(let [header (if skip-header (vector) (vector header-row div-row))]
(println (apply str (interpose \newline (into header data-rows)))))))))

(defn print-help [& _]
(println (str/trim "
Usage: bbin <command>
Expand Down

0 comments on commit beaa047

Please sign in to comment.