{"id":13442376,"url":"https://github.com/clojure-emacs/sayid","last_synced_at":"2025-05-16T11:03:50.420Z","repository":{"id":9326262,"uuid":"43218098","full_name":"clojure-emacs/sayid","owner":"clojure-emacs","description":"A debugger for Clojure","archived":false,"fork":false,"pushed_at":"2022-03-30T17:27:29.000Z","size":619,"stargazers_count":409,"open_issues_count":15,"forks_count":27,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-04-09T06:06:47.177Z","etag":null,"topics":["cider","clojure","debugger","emacs","nrepl","nrepl-middleware"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/clojure-emacs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-26T18:43:17.000Z","updated_at":"2024-12-25T14:59:39.000Z","dependencies_parsed_at":"2022-09-15T09:12:58.053Z","dependency_job_id":null,"html_url":"https://github.com/clojure-emacs/sayid","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-emacs%2Fsayid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-emacs%2Fsayid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-emacs%2Fsayid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/clojure-emacs%2Fsayid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/clojure-emacs","download_url":"https://codeload.github.com/clojure-emacs/sayid/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253371107,"owners_count":21898002,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cider","clojure","debugger","emacs","nrepl","nrepl-middleware"],"created_at":"2024-07-31T03:01:45.003Z","updated_at":"2025-05-16T11:03:50.398Z","avatar_url":"https://github.com/clojure-emacs.png","language":"Clojure","funding_links":[],"categories":["Clojure"],"sub_categories":[],"readme":"![Sayid logo](./sayid-logo.png)\n\n----------\n[![CircleCI](https://circleci.com/gh/clojure-emacs/sayid/tree/master.svg?style=svg)](https://circleci.com/gh/clojure-emacs/sayid/tree/master)\n[![Clojars Project](https://img.shields.io/clojars/v/com.billpiel/sayid.svg)](https://clojars.org/com.billpiel/sayid)\n[![cljdoc badge](https://cljdoc.org/badge/com.billpiel/sayid)](https://cljdoc.org/d/com.billpiel/sayid/CURRENT)\n\nSayid *(siy EED)* is an omniscient debugger and profiler for Clojure. It extracts secrets from code at run-time.\n\nSayid works by intercepting and recording the inputs and outputs of\nfunctions. It can even record function calls that occur inside of\nfunctions. The user can select which functions to trace. Functions can\nbe selected individually or by namespace. The recorded data can be\ndisplayed, queried and profiled.\n\nSayid currently has three components:\n\n* `sayid.core` and its supporting namespaces\n* [nREPL](https://nrepl.org) middleware\n* A [CIDER](https://cider.mx) plugin\n\nThe `sayid.core` namespace is designed to be used directly via a REPL and does\nnot require Emacs or CIDER. **BUT** the CIDER integration offers a far\nbetter experience, so it is the current focus of this page and my\ndevelopment efforts.\n\n**We're looking for more maintainers for the project. If you're interested in helping out please ping @bbatsov.**\n\n## Installation \u0026 Requirements\n\n### Requirements\n\nBasic usage requires Clojure 1.7 and the optional nREPL middleware requires nREPL 0.4+.\n\nnREPL-powered editor plugins are encouraged to make use of the bundled middleware that\nprovides a very flexible Sayid API.\n\n### Leiningen\n\nAdd this to the dependencies in your project.clj or lein profiles.clj:\n\n    [com.billpiel/sayid \"0.1.0\"]\n\nTo use the bundled nREPL middleware, you'll want to include Sayid as a\nplug-in. Here's an example of a bare-bones profiles.clj that works for\nme:\n\n```clojure\n{:user {:plugins [[com.billpiel/sayid \"0.1.0\"]]}}\n```\n\n### Clojure CLI - deps.edn\n\nAdd a the Sayid dependency to your `:deps` key. Depending on your\ndesired setup, you may want to add it to an optional profile, or your\ntools.deps config directory (often `$HOME/.clojure`).\n\n```clojure\n{:deps\n  {com.billpiel/sayid {:mvn/version \"0.1.0\"}}}\n```\n\n### Emacs Integration\n\nCIDER setup also requires that the Emacs package `sayid` is installed. It's\navailable on [MELPA](https://melpa.org/#/sayid) and [MELPA\nStable](https://stable.melpa.org/#/sayid). Put this code in `init.el`, or\nsomewhere, to load keybindings for clojure-mode buffers.\n\n```elisp\n(eval-after-load 'clojure-mode\n  '(sayid-setup-package))\n```\n\nIf you use CIDER's jack-in commands, then Sayid automatically adds the\nMaven dependency when starting a REPL. This means you don't need to\nmanually add the dependency to your `project.clj` or `deps.edn` file.\n\nIf you don't use CIDER's jack-in commands, you'll need to add a\ndependency manually. Here's an example of a bare-bones profiles.clj\nthat works for me:\n\n```clojure\n{:user {:plugins [[cider/cider-nrepl \"0.25.3\"]\n                  [com.billpiel/sayid \"0.1.0\"]]\n        :dependencies [[nrepl/nrepl \"0.7.0\"]]}}\n```\n\nUsually you'll want to use the latest versions of `cider-nrepl` and nREPL here.\n\n### Other Editors\n\nA 3rd-party vim plugin also exists. See\n[this](http://arsenerei.com/blog/posts/2017-02-24-vim-sayid/) and\n[this](https://github.com/arsenerei/vim-sayid).\n\n## Using Sayid\n\n**Note: This assumes you're using the official CIDER plugin.**.\n\nDocumentation is a little light at the moment. There are lists of\nkeybindings. Helpfully, they are easily accessible from within emacs.\nBelow are the contents of the various help buffers, as well as\ninstructions on how to pop them up in time of need.\n\nGenerated docs are also available for the core namespace [here](doc).\n\nIn a clojure-mode buffer, press `C-c s h` (`sayid-show-help`) to\npop up the help buffer.\n\n    C-c s ! -- Disable traces, eval current buffer, enable traces, clear the workspace log\n    C-c s e -- Enables traces, evals the expression at point, disables traces, displays results with terse view\n    C-c s f -- Queries the active workspace for entries that most closely match the context of the cursor position\n    C-c s n -- Applies an inner trace to the function at point, replays workspace, displays results\n    C-c s r -- Replays workspace, queries results by context of cursor\n    C-c s w -- Shows workspace, using the current view\n    C-c s t y -- Prompts for a dir, recursively traces all ns's in that dir and subdirs\n    C-c s t p -- Prompts for a pattern (* = wildcare), and applies a trace to all *loaded* ns's whose name matches the patten\n    C-c s t b -- Trace the ns in the current buffer\n    C-c s t e -- Enable the *existing* (if any) trace of the function at point\n    C-c s t E -- Enable all traces\n    C-c s t d -- Disable the *existing* (if any) trace of the function at point\n    C-c s t D -- Disable all traces\n    C-c s t n -- Apply an inner trace to the symbol at point\n    C-c s t o -- Apply an outer trace to the symbol at point\n    C-c s t r -- Remove existing trace from the symbol at point\n    C-c s t K -- Remove all traces\n    C-c s c -- Clear the workspace trace log\n    C-c s x -- Blow away workspace -- traces and logs\n    C-c s s -- Popup buffer showing what it currently traced\n    C-c s S -- Popup buffer showing what it currently traced in buffer's ns\n    C-c s V s -- Set the view\n    C-c s h -- show this help\n\n\nIn the `*sayid*` buffer, press `h` to pop up the help buffer.\n\n    ENTER -- pop to function\n    d -- def value to $s/*\n    f -- query for calls to function\n    F -- query for calls to function with modifier\n    i -- show only this instance\n    I -- query for this instance with modifier\n    w -- show full workspace trace\n    n -- jump to next call node\n    N -- apply inner trace and reply workspace\n    p -- jump to prev call node\n    P -- pretty print value\n    C -- clear workspace trace log\n    v -- toggle view\n    V -- set view (see register-view)\n    l, backspace -- previous buffer state\n    L, S-backspace -- forward buffer state\n    g -- generate instance expression and put in kill ring\n    h -- help\n\n\nIn the `*sayid-traced*` buffer, press `h` to pop up the help\nbuffer.\n\n    enter -- Drill into ns at point\n    e -- Enable trace\n    d -- Disable trace\n    E -- Enable ALL traces\n    D -- Disable ALL traces\n    i -- Apply inner trace to func at point\n    o -- Apply outer trace to func at point\n    r -- Remove trace from fun at point\n    l, backspace -- go back to trace overview (if in ns view)\n\n\nIn the `*sayid-pprint*` buffer, press `h` to pop up the help\nbuffer.\n\n    ENTER -- show path in mini-buffer\n    i -- jump into child node\n    o -- jump out to parent node\n    n -- jump to next sibling node\n    p -- jump to previous sibling node\n\n## Demos\n\n### Conj 2016 Presentation\n\nI [presented Sayid](https://www.youtube.com/watch?v=ipDhvd1NsmE) at the Clojure\nConj conference in Austin in 2016.\n\n[![Becoming Omniscient with Sayid - Bill\nPiel](http://img.youtube.com/vi/ipDhvd1NsmE/0.jpg)](http://www.youtube.com/watch?v=ipDhvd1NsmE\n\"Becoming Omniscient with Sayid - Bill Piel\")\n\n### Demo \\#1 - Video\n\nA [demo video](https://www.youtube.com/watch?v=wkduA4py-qk) I recorded after the\nvery first alpha release. You can find the [contrived\nexample](http://github.com/bpiel/contrived-example) project here.\n\n[![Sayid v0.0.1 - Demo\n#1](http://img.youtube.com/vi/wkduA4py-qk/0.jpg)](http://www.youtube.com/watch?v=wkduA4py-qk\n\"Sayid v0.0.1 - Demo #1\")\n\n### Demo \\#1 - Walkthrough\n\nThis is a written walkthrough of the same steps illustrated in the demo\nvideo above, but with Sayid v0.0.8. You can find the [contrived\nexample](http://github.com/bpiel/contrived-example) project here.\n\nBelow is the code to the test namespace. You can see that we have a\nvending machine that dispenses tacos for 85 cents. We execute the\n`test1` function, which inserts 41 cents worth of change and presses the\ntaco button.\n\n```clojure\n(ns contrived-example.core-test\n  (:require [clojure.test :refer :all]\n            [contrived-example.core :as ce]))\n\n\n(def test-vending-machine {:inventory {:a1 {:name :taco\n                                            :price 0.85\n                                            :qty 10}}\n                           :coins-inserted []\n                           :coins-returned []\n                           :dispensed nil\n                           :err-msg nil})\n\n(defn test1 []\n  (-\u003e test-vending-machine\n      (ce/insert-coin :quarter) ;; 25\n      (ce/insert-coin :dime)    ;; 35\n      (ce/insert-coin :nickel)  ;; 40\n      (ce/insert-coin :penny)   ;; 41 cents\n      (ce/press-button :a1)))   ;; taco costs 85 cents\n\n(test1)\n```\n\nLet's press some keys to get Sayid going.\n\neval the namespace `C-c C-k` (probably) (`cider-load-buffer`)\n\ntrace the project namespaces [C-c s t p]{.kbd}\n(`sayid-trace-ns-by-pattern`) then `contrived-example.*`\n\nThis should pop up. It shows how many functions have been traced in\nwhich namespaces. Execute `test1`!\n\n    Traced namespaces:\n      5 / 5  contrived-example.core\n      1 / 1  contrived-example.core-test\n      8 / 8  contrived-example.inner-workings\n\n\n    Traced functions:\n\nYou can't tell yet, but something magical happened. Press `C-c s\nw` (`sayid-get-workspace`) to get an overview of what has been\ncaptured in the Sayid workspace. This monster should appear:\n\n    v contrived-example.core-test/test1  :13446\n    |v contrived-example.core/insert-coin  :13447\n    |^\n    |v contrived-example.core/insert-coin  :13448\n    |^\n    |v contrived-example.core/insert-coin  :13449\n    |^\n    |v contrived-example.core/insert-coin  :13450\n    |^\n    |v contrived-example.core/press-button  :13451\n    ||v contrived-example.inner-workings/valid-selection  :13452\n    |||v contrived-example.inner-workings/get-selection  :13453\n    |||^\n    |||v contrived-example.inner-workings/calc-coin-value  :13454\n    |||^\n    ||| contrived-example.inner-workings/valid-selection  :13452\n    ||^\n    ||v contrived-example.inner-workings/process-transaction  :13455\n    |||v contrived-example.inner-workings/get-selection  :13456\n    |||^\n    |||v contrived-example.inner-workings/calc-change-to-return  :13457\n    ||||v contrived-example.inner-workings/calc-coin-value  :13458\n    ||||^\n    ||||v contrived-example.inner-workings/round-to-pennies  :13459\n    ||||^\n    ||||v contrived-example.inner-workings/calc-change-to-return*  :13460\n    |||||v contrived-example.inner-workings/calc-coin-value  :13461\n    |||||^\n    |||||v contrived-example.inner-workings/calc-change-to-return*  :13462\n    ||||||v contrived-example.inner-workings/calc-coin-value  :13463\n    ||||||^\n    ||||||v contrived-example.inner-workings/calc-change-to-return*  :13464\n    |||||||v contrived-example.inner-workings/calc-coin-value  :13465\n    |||||||^\n    |||||||v contrived-example.inner-workings/calc-change-to-return*  :13466\n    ||||||||v contrived-example.inner-workings/calc-coin-value  :13467\n    ||||||||^\n    |||||||| contrived-example.inner-workings/calc-change-to-return*  :13466\n    |||||||^\n    |||||||v contrived-example.inner-workings/calc-change-to-return*  :13468\n    ||||||||v contrived-example.inner-workings/calc-coin-value  :13469\n    ||||||||^\n    |||||||| contrived-example.inner-workings/calc-change-to-return*  :13468\n    |||||||^\n    |||||||v contrived-example.inner-workings/calc-change-to-return*  :13470\n    ||||||||v contrived-example.inner-workings/calc-coin-value  :13471\n    ||||||||^\n    |||||||| contrived-example.inner-workings/calc-change-to-return*  :13470\n    |||||||^\n    ||||||| contrived-example.inner-workings/calc-change-to-return*  :13464\n    ||||||^\n    |||||| contrived-example.inner-workings/calc-change-to-return*  :13462\n    |||||^\n    ||||| contrived-example.inner-workings/calc-change-to-return*  :13460\n    ||||^\n    |||| contrived-example.inner-workings/calc-change-to-return  :13457\n    |||^\n    ||| contrived-example.inner-workings/process-transaction  :13455\n    ||^\n    || contrived-example.core/press-button  :13451\n    |^\n    | contrived-example.core-test/test1  :13446\n    ^\n\nWhat's the meaning of this? These are all the function calls that were\nmade in the traced namespaced when we execute `test1`.\n\nLet's explore. Get your cursor to the first line of the output and\npress `i` (`sayid-query-id`).\n\n     v contrived-example.core-test/test1  :13446\n     | returned =\u003e  {:inventory {:a1 {:name :taco :price 0.85 :qty 9}}\n     |               :coins-inserted []\n     |               :coins-returned [:quarter :quarter :nickel]\n     |               :dispensed {:name :taco :price 0.85 :qty 10}\n     |               :err-msg nil}\n     ^\n\n\nThis shows us the details of that ***i**nstance* of `test1` being\ncalled. We can see that a hash map was returned. Despite us inserting\nonly 41 cents for an 85 cent taco, we see that a taco was dispensed,\nplus change! That's a BUG.\n\nHit `backspace` (`sayid-buf-back`). We're back at the overview.\nScan the list of functions that are called. Let's assume some\nprogrammer's intuition and decide that `valid-selection` is the first\nplace of interest. Get your cursor to that line and press these keys to\nview the ***i**nstance* and all of its ***d**escendants*. `I`\n`d` `ENTER (`sayid-query-id-w-mode`)\n\n     ||v contrived-example.inner-workings/valid-selection  :13452\n     ||| machine =\u003e {:inventory {:a1 {:name :taco :price 0.85 :qty 10}}\n     |||             :coins-inserted [:quarter :dime :nickel :penny]\n     |||             :coins-returned []\n     |||             :dispensed nil\n     |||             :err-msg nil}\n     ||| button =\u003e :a1\n     ||| returns =\u003e  true\n     |||v contrived-example.inner-workings/get-selection  :13453\n     |||| machine =\u003e {:inventory {:a1 {:name :taco :price 0.85 :qty 10}}\n     ||||             :coins-inserted [:quarter :dime :nickel :penny]\n     ||||             :coins-returned []\n     ||||             :dispensed nil\n     ||||             :err-msg nil}\n     |||| button =\u003e :a1\n     |||| returned =\u003e  {:name :taco :price 0.85 :qty 10}\n     |||^\n     |||v contrived-example.inner-workings/calc-coin-value  :13454\n     |||| coins =\u003e [:quarter :dime :nickel :penny]\n     |||| returned =\u003e  1.4\n     |||^\n     ||| contrived-example.inner-workings/valid-selection  :13452\n     ||| machine =\u003e {:inventory {:a1 {:name :taco :price 0.85 :qty 10}}\n     |||             :coins-inserted [:quarter :dime :nickel :penny]\n     |||             :coins-returned []\n     |||             :dispensed nil\n     |||             :err-msg nil}\n     ||| button =\u003e :a1\n     ||| returned =\u003e  true\n     ||^\n\nWe can see that `valid-selection` makes calls to `get-selection` and\n`calc-coin-value`. Looking at the return values, we might notice a\nproblem: `calc-coin-value` receives\n`[:quarter :dime :nickel                       :penny]` but returns\n\\$1.40 as the value. Let's dig deeper. Press `n`\n(`sayid-buffer-nav-to-next`) a couple times to get the cursor to the\ncall to `calc-coin-value`. Now press `N`\n(`sayid-buf-replay-with-inner-trace`) and hold onto your hat.\n\n     ||||v (-\u003e\u003e coins (keep coin-values) (apply +)) =\u003e (apply + (keep coin-values coins))  contrived-example.inner-workings/calc-coin-value  :13491\n     ||||| returns =\u003e  1.4\n     |||||v (apply + (keep coin-values coins))  contrived-example.inner-workings/calc-coin-value  :13492\n     |||||| #function[clojure.core/+]\n     |||||| (0.25 0.1 0.05 1)\n     |||||| returns =\u003e  1.4\n     ||||||v (keep coin-values coins)  contrived-example.inner-workings/calc-coin-value  :13493\n     ||||||| {:quarter 0.25 :dime 0.1 :nickel 0.05 :penny 1}\n     ||||||| [:quarter :dime :nickel :penny]\n     ||||||| returned =\u003e  (0.25 0.1 0.05 1)\n     ||||||^\n     |||||| (apply + (keep coin-values coins))  contrived-example.inner-workings/calc-coin-value  :13492\n     |||||| #function[clojure.core/+]\n     |||||| (0.25 0.1 0.05 1)\n     |||||| returned =\u003e  1.4\n     |||||^\n     ||||| (-\u003e\u003e coins (keep coin-values) (apply +)) =\u003e (apply + (keep coin-values coins))  contrived-example.inner-workings/calc-coin-value  :13491\n     ||||| returned =\u003e  1.4\n     ||||^\n    ...truncated...\n\n*(jump to the top of the buffer)*\n\nWhat did we do? We applied an *inner trace* to the function\n`calc-coin-value` and then replayed the call to `test1` that we had\ncaptured originally.\n\n**An INNER trace?** YES! We can see the inputs and output values of\neach expression in the function. Look at it. Where do things go wrong?\nIt's when we pass a hash map to `keep` that defines a penny as being\nworth a dollar. Bug located! Press `n` a couple times to get your\ncursor to that call. Press `RET` to jump to that line of code.\n\n```clojure\n (ns contrived-example.inner-workings)\n\n (def coin-values\n   {:quarter 0.25\n    :dime 0.10\n    :nickel 0.05\n    :penny 1})\n\n (defn- calc-coin-value\n   [coins]\n   (-\u003e\u003e coins\n        (keep coin-values)\n        (apply +)))\n\n...truncated...\n```\n\nWe now find ourselves at the troublesome call to `keep` causing our bug.\nThe hash map, `coin-values`, is just above. Change the value of a penny\nfrom `1` to `0.01`. Let's eval our corrected code the Sayid way \\--\npress `C-c s !` (`sayid-load-enable-clear`). This will remove the\ntraces, eval the buffer, then re-apply the traces. It also clears the\nworkspace log. This is all helpful. Navigate back to `core-test` and run\n`test1` again. Repeating steps above, you can verify the output is now\ncorrect: no taco.\n\n     v contrived-example.core-test/test1  :13579\n     | returned =\u003e  {:inventory {:a1 {:name :taco :price 0.85 :qty 10}}\n     |               :coins-inserted [:quarter :dime :nickel :penny]\n     |               :coins-returned []\n     |               :dispensed nil\n     |               :err-msg true}\n     ^\n\nGreat work!\n\n## License\n\nDistributed under the Apache 2.0 License. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojure-emacs%2Fsayid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclojure-emacs%2Fsayid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclojure-emacs%2Fsayid/lists"}