Skip to content

Latest commit

 

History

History
238 lines (178 loc) · 7.79 KB

simple-app.org

File metadata and controls

238 lines (178 loc) · 7.79 KB

Simple App

This is an example of a simple Clojure app written using Literate programming paradigm using org-mode.

Setting up the project

First we setup the project. In the source block bellow you can see that the language is set to zsh. This tells babel that this snippet should be executed within a shell. I also set the :results header argument to silent because in this case we don’t care about the output of running this snippet.

mkdir -p simple-app/src/simple_app
mkdir -p simple-app/test/simple_app
mkdir -p simple-app/dev

Now we create our basic deps file. Note that our source block name is set to clojure. We do this because that will make the file sintax hilighting work.

To save a snippet like this on a path we set the :tangle header argument and when we run the emacs command org-babel-tangle our file will be saved to the path specified.

{:deps
 {org.clojure/clojure {:mvn/version "1.10.1"}}
 :aliases
 {:test
  {:extra-paths ["test"]
   :extra-deps {com.cognitect/test-runner
                {:git/url "https://github.com/cognitect-labs/test-runner.git"
                 :sha "209b64504cb3bd3b99ecfec7937b358a879f55c1"}}
   :main-opts ["-m" "cognitect.test-runner"]}}}

Running and evaluating code

Now that we have our basic project setup we can work on our project namespaces.

First we start by starting a REPL. There are a few different ways you can approach this. If you are in emacs you could simply run cider-jack-in but if you notice this file isn’t in the same directory as the project. That means cider wont know what deps.edn file to load.

A solution for this is to manually run your REPL command. Notice the :dir header variable bellow. It tells Emacs the relative path of where to execute the command. A quirk of doing this is that the following command is blocking and Emacs seems to freeze on waiting for output. You can C-g to unfreeze the editor. Notice we set :session so that our future zsh commands dont get blocked.

clojure -R:test -C:test -Sdeps '{:deps {nrepl {:mvn/version "0.7.0"} cider/cider-nrepl {:mvn/version "0.25.2"}}}' -m nrepl.cmdline --middleware '["cider.nrepl/cider-middleware"]'

Lets fetch that nrepl port.

cat .nrepl-port

Now we run cider-connect-clj and we can start working on our project.

Lets start with a simple test.

(require '[clojure.test :refer [deftest testing is]])

(deftest a-test
  (testing "FIXME, I fail."
    (is (= 0 1))))

Since we are not in a typical dev environment there are a few different ways to run clojure code. Notice that we need to set :epilogue

We could run them as if we were in the command line, but make sure you org-babel-tangle first. Notice that we set :epilogue to “true”. This is a weird quirk we have to do because the output returns to standard error so Emacs thinks there is no output. We also set results as a pp so we can comfortably collapse the results.

clj -A:test
Running tests in #{"test"}

Testing simple-app.core-test

FAIL in (a-test) (core_test.clj:6)
FIXME, I fail.
expected: (= 0 1)
  actual: (not (= 0 1))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.

Ideally, we should really try to evaluate code already directly on our REPL.

(run-tests 'simple-app.core-test)
Testing simple-app.core-test

FAIL in (a-test) (NO_SOURCE_FILE:6)
FIXME, I fail.
expected: (= 0 1)
  actual: (not (= 0 1))

Ran 1 tests containing 1 assertions.
1 failures, 0 errors.

Notice that we set :results to output. This tells Emacs to get the value from standard out which is way more informative than the return value of the run-tests function. In this case the function returns a map of values like:

{:test 1, :pass 0, :fail 1, :error 0, :type :summary}

I started with tests to score some karma points but this technique really shines is in the ability to interactively evaluate code and see the results inline. So lets do more of that now.

(defn hello [subject]
  (prn (str "Hello " subject)))
(hello "World")
"Hello World"

First of notice we are still setting results to “output”. This is because this function prints to standard out. Thats not something new, but checkout the :tangle header. The value is set to a “scratch.clj” file in the dev directory. That file will now become a bucket where all the one off commands I run will go to. I thought this was a neat concept as I can go back to that file and just see code int the order I meant to execute it without all the documentation.

Now lets play a bit more with that :results header. We are going to se a few examples here for mori information you can go to the official docs.

The default value for results is “value”. Wich plainly means the result of the evaluation will be printed in the documet. There is an anoying quirk (or bug?) that with clojure evaluation where the result is printed only from the first form. I dont mind this so much as the idea of this approach is to have documentation and evaluation step by step.

(str "foo")(+ 1)(prn "baz")
foo

If the evaluation returns something that looks like a list we can use :results value list

["foo" "bar" "baz"]
  • foo
  • bar
  • baz

Org will try to print things like sequences of sequences as tables by default but it can be forced by setting :results value list

[["foo" "bar" "baz" "qux"]
 ["1" "2" "3" "4"]
 [1 2 3 4]
 '(:one :two :three :four)
 (take 4 (range))]
foobarbazqux
1234
1234
:one:two:three:four
0123

I could not find a super clean way to print maps nicely. Fortunately clojure pprint has us covered.

(clojure.pprint/print-table [{:a 1 :b 2 :c 3} {:b 5 :a 7 :c "dog"}])
| :a | :b |  :c |
|----+----+-----|
|  1 |  2 |   3 |
|  7 |  5 | dog |

There is so much more you can do with the :results header like printing images, raw data, org code blocks, drawers, etc. Be shure to look at the docs and play around on your own.

One last tip I want to share is that you might want to edit clojure blocks in clojure mode. To achieve this there is a nifty org-edit-special command. This command will put your snippet in an Emacs pop-up in clojure mode.

(defn -main [target]
  (hello target))

Run org-babel-tangle and org-babel-execute-buffer and profit.

clojure -m simple-app.core "Clojure North"

There you have it a fully running Clojure project in one file.