├── .circleci └── config.yml ├── .cljstyle ├── .gitignore ├── CHANGELOG.md ├── HISTORY.md ├── README.md ├── UNLICENSE ├── demo.gif ├── project.clj └── src └── whidbey ├── plugin.clj ├── repl.clj └── types.clj /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | # Common executor configuration 4 | executors: 5 | clojure: 6 | docker: 7 | - image: circleci/clojure:openjdk-8-lein-2.9.1 8 | working_directory: ~/repo 9 | 10 | 11 | # Job definitions 12 | jobs: 13 | style: 14 | executor: clojure 15 | steps: 16 | - checkout 17 | - run: 18 | name: Install cljstyle 19 | environment: 20 | CLJSTYLE_VERSION: 0.10.1 21 | command: | 22 | wget https://github.com/greglook/cljstyle/releases/download/${CLJSTYLE_VERSION}/cljstyle_${CLJSTYLE_VERSION}_linux.tar.gz 23 | tar -xzf cljstyle_${CLJSTYLE_VERSION}_linux.tar.gz 24 | - run: 25 | name: Check source formatting 26 | command: "./cljstyle check --stats style-stats.tsv" 27 | - store_artifacts: 28 | path: style-stats.tsv 29 | destination: style 30 | 31 | check: 32 | executor: clojure 33 | steps: 34 | - checkout 35 | - run: lein check 36 | 37 | 38 | # Workflow definitions 39 | workflows: 40 | version: 2 41 | test: 42 | jobs: 43 | - style 44 | - check 45 | -------------------------------------------------------------------------------- /.cljstyle: -------------------------------------------------------------------------------- 1 | ;; vim: filetype=clojure 2 | {:padding-lines 2 3 | :max-consecutive-blank-lines 3 4 | :file-ignore #{".git" "target"}} 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | All notable changes to this project will be documented in this file, which 5 | follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 6 | This project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [Unreleased] 9 | 10 | ... 11 | 12 | 13 | ## [2.2.1] - 2020-06-01 14 | 15 | ### Changed 16 | - Upgrade puget to 1.3.1. 17 | 18 | 19 | ## [2.2.0] - 2019-10-11 20 | 21 | ### Changed 22 | - Upgrade puget to 1.2.0 to fix a few issues. 23 | - Remove dependency on `org.clojure/data.codec`. 24 | 25 | 26 | ## [2.1.1] - 2019-03-18 27 | 28 | ### Changed 29 | - Upgrade puget to 1.1.1 for a ~3x speedup sorting complex collections. 30 | 31 | 32 | ## [2.1.0] - 2019-03-01 33 | 34 | ### Fixed 35 | - Add support for nrepl 0.6.0 which is used by leiningen 2.9.0. 36 | [#27](//github.com/greglook/whidbey/issues/27) 37 | [#28](//github.com/greglook/whidbey/pull/28) 38 | - The `:init` and `:custom-init` forms in `:repl-options` are preserved in 39 | whidbey's generated profile. 40 | [#21](//github.com/greglook/whidbey/issues/21) 41 | 42 | 43 | ## [2.0.0] - 2018-12-16 44 | 45 | This is a major version bump to switch between `org.clojure/tools.nrepl` and the 46 | newer stand-alone `nrepl` project. This release requires leiningen `2.8.2` or 47 | higher to get the right nREPL version. 48 | 49 | ### Changed 50 | - Drop deprecated implicit middleware in favor of explicit 51 | `amperity.plugin/repl-pprint`. 52 | - Switch from nREPL contrib to new independent project. 53 | - Drop custom `render-values` nREPL middleware and var haxin. 54 | 55 | ### Added 56 | - Whidbey uses the new nREPL `:printer` framework and respects per-message 57 | `:print-options` overrides. 58 | 59 | 60 | ## [1.3.1] - 2016-10-07 61 | 62 | ### Changed 63 | - Upgrade `mvxcvi/puget` to 1.0.1 64 | 65 | 66 | ## [1.3.0] - 2015-11-03 67 | 68 | ### Changed 69 | - Change from `:print-handlers` to `:tag-types` using nested maps. This makes 70 | Leiningen profile config more composable since maps are recursively merged. 71 | 72 | 73 | ## [1.2.0] - 2015-10-29 74 | 75 | ### Changed 76 | - Upgrade Puget to 1.0.0. 77 | 78 | 79 | ## [1.1.1] - 2015-10-20 80 | 81 | ### Changed 82 | - Upgrade Puget to 0.9.2. 83 | 84 | ### Fixed 85 | - Use `puget/unknown-handler` for escaped types instead of `pr-handler`. 86 | 87 | 88 | ## [1.1.0] - 2015-10-18 89 | 90 | ### Changed 91 | - Upgrade Puget to 0.9.1. 92 | - Keep a `PrettyPrinter` record instead of a raw options map. 93 | - Update to use new Puget print-handler logic. 94 | 95 | 96 | ## [1.0.0] - 2015-04-26 97 | 98 | First "stable" release! 99 | 100 | ### Changed 101 | - Upgrade Puget to 0.8.1. 102 | - Use new Leiningen `:repl` metadata logic to load plugin profile. 103 | 104 | 105 | ## [0.6.0] - 2015-03-11 106 | 107 | ### Added 108 | - Add `whidbey.repl` namespace to handle initialization. 109 | - Dependency versions are now matched to the plugins in the project map. 110 | 111 | ### Changed 112 | - Use `:whidbey` for project map key instead of `:puget-options`. 113 | - Upgrade Puget to 0.8.0. 114 | 115 | ### Fixed 116 | - Remove side-effects from loading `render-values` namespace. 117 | 118 | 119 | ## [0.5.1] - 2015-02-28 120 | 121 | ### Changed 122 | - Upgrade Puget to 0.7.1. 123 | 124 | 125 | ## [0.5.0] - 2015-02-11 126 | 127 | ### Changed 128 | - Upgrade Puget to 0.7.0. 129 | - Upgrade tools.nrepl to 0.2.7. 130 | 131 | 132 | ## [0.4.2] - 2014-12-28 133 | 134 | ### Changed 135 | - Upgrade Puget to 0.6.6. 136 | 137 | 138 | ## [0.4.1] - 2014-10-14 139 | 140 | ### Changed 141 | - Upgrade tools.nrepl to 0.2.6. 142 | 143 | 144 | ## [0.4.0] - 2014-10-13 145 | 146 | ### Changed 147 | - Leiningen plugin profile is now namespaced. 148 | [#12](//github.com/greglook/whidbey/issues/12) 149 | - Upgrade Puget to 0.6.4. 150 | - Use separate options from Puget to prevent interference. 151 | - Prevent nREPL's built-in `pr-values` middleware from causing problems by 152 | replacing it with `identity`. 153 | 154 | 155 | ## [0.3.3] - 2014-09-23 156 | 157 | ### Added 158 | - Allow coloring to be disabled via `:puget-options`. 159 | [#9](//github.com/greglook/whidbey/issues/9) 160 | 161 | 162 | ## [0.3.2] - 2014-06-26 163 | 164 | ### Added 165 | - Pass `:puget-options` in project map to renderer. 166 | 167 | ### Changed 168 | - Assume `puget.printer/cprint-str` as the rendering function. 169 | 170 | 171 | ## [0.2.2] - 2014-06-19 172 | 173 | ### Changed 174 | - Upgrade Puget to 0.5.2 175 | 176 | ### Fixed 177 | - Fix issue when user had existing `:repl` profile customizations. 178 | 179 | 180 | ## [0.2.0] - 2014-05-14 181 | 182 | ### Added 183 | - Add `whidbey.plugin` namespace to automatically enable rendering in Leiningen 184 | `repl` tasks. 185 | 186 | [Unreleased]: https://github.com/greglook/whidbey/compare/2.2.1...HEAD 187 | [2.2.1]: https://github.com/greglook/whidbey/compare/2.2.0...2.2.1 188 | [2.2.0]: https://github.com/greglook/whidbey/compare/2.1.1...2.2.0 189 | [2.1.1]: https://github.com/greglook/whidbey/compare/2.1.0...2.1.1 190 | [2.1.0]: https://github.com/greglook/whidbey/compare/2.0.0...2.1.0 191 | [2.0.0]: https://github.com/greglook/whidbey/compare/1.3.1...2.0.0 192 | [1.3.1]: https://github.com/greglook/whidbey/compare/1.3.0...1.3.1 193 | [1.3.0]: https://github.com/greglook/whidbey/compare/1.2.0...1.3.0 194 | [1.2.0]: https://github.com/greglook/whidbey/compare/1.1.1...1.2.0 195 | [1.1.1]: https://github.com/greglook/whidbey/compare/1.1.0...1.1.1 196 | [1.1.0]: https://github.com/greglook/whidbey/compare/1.0.0...1.1.0 197 | [1.0.0]: https://github.com/greglook/whidbey/compare/0.6.0...1.0.0 198 | [0.6.0]: https://github.com/greglook/whidbey/compare/0.5.1...0.6.0 199 | [0.5.1]: https://github.com/greglook/whidbey/compare/0.5.0...0.5.1 200 | [0.5.0]: https://github.com/greglook/whidbey/compare/0.4.2...0.5.0 201 | [0.4.2]: https://github.com/greglook/whidbey/compare/0.4.1...0.4.2 202 | [0.4.1]: https://github.com/greglook/whidbey/compare/0.4.0...0.4.1 203 | [0.4.0]: https://github.com/greglook/whidbey/compare/0.3.3...0.4.0 204 | [0.3.3]: https://github.com/greglook/whidbey/compare/0.3.2...0.3.3 205 | [0.3.2]: https://github.com/greglook/whidbey/compare/0.2.2...0.3.2 206 | [0.2.2]: https://github.com/greglook/whidbey/compare/0.2.0...0.2.2 207 | [0.2.0]: https://github.com/greglook/whidbey/compare/0.1.0...0.2.0 208 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | Motivation and History 2 | ====================== 3 | 4 | As I develop Clojure and interact with the REPL, I frequently used 5 | `clojure.pprint` to get a better display of the results of my commands. Later, I 6 | wrote [Puget](https://github.com/greglook/puget) to pretty print in a canonical 7 | fashion with ANSI coloring. Soon I found myself running this after almost every 8 | command: 9 | 10 | ```clojure 11 | (cprint *1) 12 | ``` 13 | 14 | I decided that it would be really nice if the REPL just pretty-printed colored 15 | values _for_ me, so I dove into the Leiningen/REPLy/nREPL stack. 16 | 17 | ## Learning to REPL 18 | 19 | When you start a REPL, the basic sequence of events looks like this: 20 | 21 | 1. Leiningen parses the `:repl-options` in your project map. 22 | 2. Leiningen starts an nREPL server with any specified custom handler or 23 | middlewares. (more on this later) 24 | 3. Leiningen starts a REPLy client with the given options and connects it to 25 | nREPL. 26 | 4. The read-eval-print-loop starts. 27 | 28 | However, you're actually interacting with a client/server model, so the text you 29 | type into the REPL isn't directly interpreted. Instead, in must be sent to the 30 | server and the result communicated back to the client. nREPL accomplishes this 31 | using messages with an `:op` key. On the server side, a _handler_ and a stack of 32 | _middleware_ functions (very much like 33 | [Ring](https://github.com/ring-clojure/ring)) process the messages and send 34 | result messages back to the client. 35 | 36 | For example, when you type a form like `(+ 1 2 3)`, REPLy sends the server a 37 | message like: 38 | 39 | ```clojure 40 | {:op "eval" 41 | :code "(+ 1 2 3)" 42 | :ns "user"} 43 | ``` 44 | 45 | nREPL's `interruptible-eval` middleware catches `eval` operations and runs a 46 | sub-REPL to read and evaluate the input. The resulting value is built into 47 | another message and sent back to the client's `Transport`: 48 | 49 | ```clojure 50 | {:ns "user" 51 | :value 6} 52 | ``` 53 | 54 | At a higher level in the middleware stack, nREPL's `pr-values` wraps the 55 | `Transport` passed to later handlers. When messages are sent, the `:value` is 56 | transformed into a string using `print-method` or `print-dup`. This is needed 57 | because the result has to be serialized back over the wire to the client, and 58 | arbitrary Clojure values are not supported. 59 | 60 | ## Towards a Solution 61 | 62 | To add enough functionality to support colored pretty-printing, it turned out to 63 | be necessary to modify REPLy, but fortunately not nREPL or Leiningen. The change 64 | looks for an `:nrepl-context` map in the options passed to the client. The 65 | values specified under `:interactive-eval` are merged with the nREPL message for 66 | interactive evaluations. 67 | 68 | It was necessary to patch REPLy because the client sends `eval` ops to the 69 | server in situations other than user input. For example, when you tab-complete a 70 | name while typing an expression, REPLy is actually evaluating a completion 71 | function on the server that searches the current namespace for matching symbols. 72 | The client then `read-string`s the response to get the list of matches. 73 | 74 | If we just pretty-printed _all_ `eval` requests, the results to requests like 75 | these would contain extra whitespace and ANSI color codes, which break the 76 | Clojure reader. We need to be able to select when to use a custom renderer and 77 | when plain strings are desirable, and the REPL client is the only place we can 78 | do that. 79 | 80 | ## Value Rendering 81 | 82 | Now we're finally in a position to pretty print our REPL values! This library 83 | provides a `render-values` middleware which replaces the built-in `pr-values`. 84 | This watches messages for the `:renderer` key, and uses it to produce the 85 | returned string value. 86 | 87 | The value of `:renderer` should be a symbol which resolves to a _rendering 88 | function_ on the server. Rendering functions accept one argument (the value to 89 | render) and return a string representation. If not provided, `render-values` 90 | falls back to `print-method` or `print-dup`, so if you don't specify anything 91 | the REPL will behave exactly as before. 92 | 93 | Now, `eval` messages are passed down the stack, handled by `interruptible-eval`, 94 | and the result sent to the `Transport` to send back to the client. The 95 | `render-values` middleware's inserted transport processes the response message 96 | by using the desired function to render the message value. In Puget's case, this 97 | means returning a string with embedded ANSI color codes. When REPLy receives 98 | this message, all it has to do is faithfully reprint the string and the user 99 | sees nicely colored and pretty-printed text. 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | whidbey 2 | ======= 3 | 4 | [![Build Status](https://circleci.com/gh/greglook/whidbey.svg?style=shield&circle-token=28b1a3241663836937ffb5df9a451e5727625c65)](https://circleci.com/gh/greglook/whidbey) 5 | [![cljdoc](https://cljdoc.org/badge/mvxcvi/whidbey)](https://cljdoc.org/d/mvxcvi/whidbey/CURRENT) 6 | 7 | This is a plugin for [Leiningen](http://leiningen.org/) which changes the REPL 8 | to pretty-print results with [Puget](https://github.com/greglook/puget). 9 | 10 | ![repl demo](demo.gif) 11 | 12 | Internally, Whidbey integrates with the [nREPL](https://github.com/nrepl/nrepl) 13 | `pr-values` middleware to provide a custom pretty-printer for the results of 14 | evaluated forms in the REPL. See the [history doc](HISTORY.md) for more on the 15 | motivations and implementation details behind this project. 16 | 17 | 18 | ## Usage 19 | 20 | To use Whidbey, add it to the `:plugins` vector in your `user` or `system` 21 | profile. Note that this requires Leiningen version 2.8.2 or higher for the 22 | necessary nREPL and plugin functionality. 23 | 24 | [![Clojars Project](http://clojars.org/mvxcvi/whidbey/latest-version.svg)](http://clojars.org/mvxcvi/whidbey) 25 | 26 | Since Leiningen has deprecated implicit plugin middleware, you'll need to 27 | activate it by ading the following to your profile as well: 28 | 29 | ```clojure 30 | :middleware [whidbey.plugin/repl-pprint] 31 | ``` 32 | 33 | ### Configuration 34 | 35 | Whidbey passes rendering options into Puget from the `:whidbey` key in the 36 | profile map: 37 | 38 | ```clojure 39 | :whidbey {:width 180 40 | :map-delimiter "" 41 | :extend-notation true 42 | :print-meta true 43 | :color-scheme {:delimiter [:blue] 44 | :tag [:bold :red] 45 | ...} 46 | ...} 47 | ``` 48 | 49 | See the [`puget.printer`](https://cljdoc.org/d/mvxcvi/puget/CURRENT/api/puget.printer) 50 | namespace for the available configuration. 51 | 52 | If you feel like adjusting Whidbey's configuration at runtime, you can use the 53 | `whidbey.repl/update-options!` function. This will affect all subsequent 54 | messages rendered. 55 | 56 | If you need to further customize the responses from the REPL, Whidbey respects 57 | any `:print-options` set on the `:op :eval` message. These will be merged into 58 | the normal rendering configuration, but will not affect subsequent messages. 59 | 60 | ### Tag Extensions 61 | 62 | Whidbey adds some convenience tagged-literal extensions for binary data and 63 | URIs. The extensions update the `default-data-readers` var to support 64 | round-tripping the tagged representations: 65 | 66 | ```clojure 67 | => (java.net.URI. "http://github.com/greglook") 68 | #whidbey/uri "http://github.com/greglook" 69 | 70 | => (.getBytes "foo bar baz") 71 | #whidbey/bin "Zm9vIGJhciBiYXo=" 72 | 73 | => #whidbey/bin "b25lIG1vcmUgdGltZSwgbXVzaWNzIGdvdCBtZSBmZWVsaW5nIHNvIGZyZWU=" 74 | #whidbey/bin "b25lIG1vcmUgdGltZSwgbXVzaWNzIGdvdCBtZSBmZWVsaW5nIHNvIGZyZWU=" 75 | ``` 76 | 77 | This is controlled by the `:extend-notation` option. Other type extensions can 78 | be added by providing a `:tag-types` map. This should map type symbols to a map 79 | with a tag symbol key pointing to a formatting function. When the type is 80 | encountered, it will be rendered as a tagged literal with a form from calling 81 | the formatter on the value. 82 | 83 | For example, to render class values as tagged types, you can add this to your 84 | `:whidbey` config: 85 | 86 | ```clojure 87 | :tag-types 88 | {java.lang.Class {'java/class #(symbol (.getName %))}}} 89 | ``` 90 | 91 | If the type name or the formatter function are not available at load time, you 92 | can quote them to suppress evaluation until those types are printed. 93 | 94 | ### Troubleshooting 95 | 96 | Sometimes, there are types which Puget has trouble rendering. These can be 97 | excluded from pretty-printing by adding their symbol to the `:escape-types` set 98 | in the options. These types will be rendered with the normal Clojure printer. 99 | If you want to use these types' `print-method` instead, set the 100 | `:print-fallback` option to `:print`: 101 | 102 | ```clojure 103 | :whidbey {:print-fallback :print 104 | :escape-types #{'datomic.db.Db 'datomic.btset.BTSet ...} 105 | ...} 106 | ``` 107 | 108 | Whidbey may also conflict with other REPL customizations. If you experience 109 | errors, you can check how the profiles are being merged using the lein-pprint or 110 | [lein-cprint](https://github.com/greglook/lein-cprint) plugins: 111 | 112 | ```bash 113 | $ lein with-profile +whidbey/repl cprint :repl-options 114 | ``` 115 | 116 | 117 | ## License 118 | 119 | This is free and unencumbered software released into the public domain. 120 | See the UNLICENSE file for more information. 121 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/greglook/whidbey/a7c2111523ec660c3b6fd25d84221b0b1d74a4b5/demo.gif -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject mvxcvi/whidbey "2.2.1" 2 | :description "nREPL middleware to allow arbitrary value rendering." 3 | :url "https://github.com/greglook/whidbey" 4 | :license {:name "Public Domain" 5 | :url "http://unlicense.org/"} 6 | 7 | :deploy-branches ["master"] 8 | :eval-in-leiningen true 9 | :min-lein-version "2.8.2" 10 | 11 | :dependencies 12 | [[mvxcvi/puget "1.3.1"]]) 13 | -------------------------------------------------------------------------------- /src/whidbey/plugin.clj: -------------------------------------------------------------------------------- 1 | (ns whidbey.plugin 2 | "This namespace runs inside of Leiningen and rewrites the project map to 3 | include the customizations provided by Whidbey." 4 | (:require 5 | [leiningen.core.project :as project])) 6 | 7 | 8 | (defn- find-plugin-version 9 | "Looks up the plugins in the project map and tries to find the version 10 | specified for the given symbol. Returns nil if none matches." 11 | [project plugin] 12 | (try 13 | (some (fn [[p v]] (when (= p plugin) v)) 14 | (:plugins project)) 15 | (catch Exception e 16 | nil))) 17 | 18 | 19 | (defn- whidbey-profile 20 | "Constructs a profile map for enabling the repl hooks." 21 | [project] 22 | (let [version (find-plugin-version project 'mvxcvi/whidbey) 23 | options (:whidbey project) 24 | {:keys [init custom-init]} (:repl-options project)] 25 | (-> `{:dependencies [[mvxcvi/whidbey ~(or version "RELEASE")]] 26 | ;; :init is run once when the server starts 27 | ;; :custom-init is run on session creation 28 | ;; ^:replace to workaround https://github.com/technomancy/leiningen/issues/878 29 | :repl-options {:init ^:replace (do ~init 30 | (require 'whidbey.repl) 31 | (whidbey.repl/init! ~options)) 32 | :custom-init ^:replace (do ~custom-init 33 | (whidbey.repl/update-print-fn!)) 34 | ;; :printer is nrepl 0.5.x only 35 | :nrepl-context {:interactive-eval {:printer whidbey.repl/render-str}}}} 36 | (vary-meta assoc :repl true)))) 37 | 38 | 39 | (defn repl-pprint 40 | "Adds a `whidbey/repl` profile to the project containing Whidbey's repl 41 | customizations. The profile is tagged with metadata which will cause it to be 42 | merged for the repl task." 43 | [project] 44 | (if (:whidbey/repl (:profiles project)) 45 | project 46 | (let [profile (whidbey-profile project)] 47 | (project/add-profiles project {:whidbey/repl profile})))) 48 | -------------------------------------------------------------------------------- /src/whidbey/repl.clj: -------------------------------------------------------------------------------- 1 | (ns whidbey.repl 2 | (:require 3 | [puget.dispatch :as dispatch] 4 | [puget.printer :as puget] 5 | [whidbey.types :as types])) 6 | 7 | 8 | (def options 9 | "Rendering options." 10 | {:print-color true 11 | :namespace-maps true 12 | :seq-limit 100 13 | :extend-notation true 14 | :escape-types #{'clj_http.headers.HeaderMap 15 | 'datomic.btset.BTSet 16 | 'datomic.db.Db}}) 17 | 18 | 19 | 20 | ;; ## Value Rendering 21 | 22 | (defn- print-handlers 23 | "Construct a custom print handler lookup function for Whidbey 24 | pretty-printing." 25 | [opts] 26 | (dispatch/chained-lookup 27 | (fn escape-handlers 28 | [t] 29 | (when (and (class? t) 30 | (seq (:escape-types opts)) 31 | (some #{(symbol (.getName ^Class t))} 32 | (:escape-types opts))) 33 | puget/unknown-handler)) 34 | (fn tag-handlers 35 | [t] 36 | (when (and t (:extend-notation opts)) 37 | (let [types (merge types/tag-types (:tag-types opts)) 38 | handlers (or (get types t) 39 | (get types (symbol (.getName ^Class t))))] 40 | (when-let [[tag formatter] (first handlers)] 41 | (puget/tagged-handler tag (if (symbol? formatter) 42 | (resolve formatter) 43 | formatter)))))) 44 | puget/common-handlers)) 45 | 46 | 47 | (defn- print-options 48 | "Construct a map of print options to pass to Puget." 49 | [opts] 50 | (let [opts (puget/merge-options options opts)] 51 | (assoc opts :print-handlers (print-handlers opts)))) 52 | 53 | 54 | (defn render 55 | "Renders the given value for display by pretty-printing it on the given writer 56 | using Puget and the configured options." 57 | ([value writer] 58 | (render value writer nil)) 59 | ([value writer opts] 60 | (binding [*out* writer] 61 | (puget/pprint value (print-options opts))))) 62 | 63 | 64 | (defn render-str 65 | "Renders the given value to a display string by pretty-printing it using Puget 66 | and the configured options." 67 | ([value] 68 | (render-str value nil)) 69 | ([value opts] 70 | (puget/pprint-str value (print-options opts)))) 71 | 72 | 73 | 74 | ;; ## Initialization 75 | 76 | (defn update-print-fn! 77 | "Updates nREPL's printing configuration to use Puget. nREPL 0.6.0+ only." 78 | [] 79 | (some-> (find-ns 'nrepl.middleware.print) 80 | (ns-resolve '*print-fn*) 81 | (var-set render))) 82 | 83 | 84 | (defn update-options! 85 | "Updates the current rendering options by merging in the supplied map." 86 | [opts] 87 | (alter-var-root #'options puget/merge-options opts)) 88 | 89 | 90 | (defn install-data-readers! 91 | "Initializes the default data-readers map to support Whidbey's custom tags." 92 | [] 93 | (alter-var-root #'default-data-readers merge types/tag-readers)) 94 | 95 | 96 | (defn init! 97 | "Initializes the repl to use Whidbey's customizations." 98 | [options] 99 | (update-options! options) 100 | (when (:extend-notation options) 101 | (install-data-readers!))) 102 | -------------------------------------------------------------------------------- /src/whidbey/types.clj: -------------------------------------------------------------------------------- 1 | (ns whidbey.types 2 | "Rendering extensions for various custom types such as byte arrays and URI 3 | strings." 4 | (:import 5 | java.net.URI 6 | java.util.Base64)) 7 | 8 | 9 | (defn bin-str 10 | "Renders a byte array as a base-64 encoded string." 11 | [^bytes bin] 12 | (-> (Base64/getUrlEncoder) 13 | (.withoutPadding) 14 | (.encodeToString bin))) 15 | 16 | 17 | (defn read-bin 18 | "Reads a base64-encoded string into a byte array. Suitable as a data-reader 19 | for `whidbey/bin` literals." 20 | ^bytes 21 | [^String bin] 22 | (.decode (Base64/getUrlDecoder) bin)) 23 | 24 | 25 | (defn read-uri 26 | "Constructs a URI from a string value. Suitable as a data-reader for 27 | `whidbey/uri` literals." 28 | ^URI 29 | [^String uri] 30 | (URI. uri)) 31 | 32 | 33 | (def tag-types 34 | "Extra print-handlers for Whidbey's repl tag extensions." 35 | {(symbol "[B") {'whidbey/bin bin-str} 36 | 'java.net.URI {'whidbey/uri str}}) 37 | 38 | 39 | (def tag-readers 40 | "Inverse mapping of tag symbols to reader functions." 41 | {'whidbey/bin read-bin 42 | 'whidbey/uri read-uri}) 43 | --------------------------------------------------------------------------------