Clojure/ClojureScript Boot literate programming template

1 Introduction

1.1 Clojure/ClojureScript/Boot literate programing project template for Emacs + Org mode

This is a skeleton of a Literate Clojure/Boot project in Emacs/Org envirenoment. It shows how to shape a literate programming project from several org-files, and no just one, and still have proper documentation and code export (tangling) both from Emacs directly as well as from project generated shell-scripts.

The project is self-documenting, get started from project.org and it should be more or less clear how to use it, or navigate to pre-exported documentation.

2 Quick start

  • Git clone from https://github.com/MaximGB/clojure-boot-literate.git.
  • Load project Library of Babel, by executing the following code block (org-babel-lob-ingest "build/lob.org"), to execute it place the cursor on the block and press C-c C-c.
  • Fill in the table in the Project specification section.
  • Read the Environment and project requirements section, setup your environment accordingly and create the code exporting scripts tangle.sh and tangle-all.sh.
  • Export initial project code by runing ./tangle-all.sh, place the cursor on the code block and press C-c C-c to execute it from Emacs, or run tangle-all.sh from a terminal.
  • Build the project with Boot:
    • For Clojure boot clj-build, place the cursor on the code block and press C-c C-c to execute it, or run boot clj-build from a terminal.
    • For ClojureScript boot cljs-figwheel, place the cursor on the code block and press C-c C-c to execute it, or run boot cljs-figwheel from a terminal.
  • Run the project by running java -jar target/project.jar, place the cursor on the code block and press C-c C-c, or run java jar target/project.jar from a terminal.
  • Keep working on the project export the code periodically.
  • To export the project documentation:
    • Into project.pdf press С-с С-e l p.
    • Into project.html press C-c C-e h h.

3 Project specification

Project specification is given in the table following. The project file system as well as boot.properties and build.boot files are generated according to the specification.

Table 1: Project specification
Property Value
Boot  
Version 2.6.0
Project name literate-boot
Project version 0.1.0-SNAPSHOT
Org sources path org
Tests path tests
Resource path resources
Assets path assets
Target path target
Main namespace core
Clojure yes
Version 1.8.0
Sources path src
ClojureScript yes
Version 1.9.225
Sources path src
Public resources path resources/public
Cider  
Version 0.14.0

3.1 Dependencies

Project dependencies are given in the dependencies table

One dependency per the table row should be given. The table columns correspond to options supported by pomegranate library, which in it's turn, is used by Boot for dependecies resolution 1:

  • Name - artifact name
  • Version - artifact version
  • Scope - dependency scope (in Maven terms), mighe be one of: compile, provided, runtime, test, system, import
  • Optional - dependency optionality flag, any dependency having no or empty value in this column is considired to be optional
  • Classifier - dependency classifier (in Maven terms)
  • Extension - dependency extension (in Maven-terms)
  • Exclusion - transitive dependencies exclusions for the dependency, one exclusion per the table row is allowed if you need to setup several exclusions for one dependency than for second and other exclusions name, version, scope, optional, classifier, extension columns should be empty
  • Classifier - exclusion dependency classifier (in Maven terms)
  • Extension - exclusion dependency extension (in Maven-terms)
  • Note - this column is ignored, it's used for documentation purposes
Table 2: Dependencies
Name Version Scope Optional Classifier Extension Exclusion Classifier Extension Note

4 Environment and project requirements

4.1 Environtment

This project template requires Emacs/Org-mode, Clojure, Boot. The project which might be built using this template will follow principles of the literate programming, i.e. when program code is extracted from Org files, which in turn combine a project documentation and code.

4.2 Code export from org-files.

To exctract code from org-files into files with Clojure source code tangle.sh file is used 2.

Unfortunatelly it's impossible to export code from Emacs by simply executing M-x org-babel-tangle call out of the box using this template. The project org-files are using the so called "Library of Babel" feature, the code of which is inside build/lob.org file. To initialize the library a call to (org-babel-lob-ingest) is required. This call is done inside tangle.sh file and if you run it from a terminal everything should work out of the box. To export code from Emacs you will have to execute (org-babel-lib-ingest "build/lob.org") call once per session (Emacs run).

To create tangle.sh file in the project's root directory place the cursor into the following source code block and press C-u C-c C-v t, this will put the code block contents into tangle.sh file on your disk.

DIR=`pwd`
FILES=""

# wrap each argument in the code required to call tangle on it
for i in $@; do
    FILES="$FILES \"$i\""
done

emacs -Q --batch \
    --eval \
    "(progn
       (require 'package)
       (let ((default-directory package-user-dir))
         (normal-top-level-add-subdirs-to-load-path))
       (require 'cl)(require 'org)(require 'ob)(require 'ob-tangle)(require 'ob-lob)
       (org-babel-lob-ingest \"build/lob.org\")
       (setq org-confirm-babel-evaluate nil)
       (mapc (lambda (file)
              (find-file (expand-file-name file \"$DIR\"))
              (org-babel-tangle)
              (kill-buffer)) '($FILES)))" \
2>&1 | grep Tangled

This script receives org-file names which to extract source code from: tangle.sh file ..., where file is name or names of org-files to process.

4.3 Project code export

To extract project source code from all org-files the tangle-all.sh shell-script is used 2.

To create tangle-all.sh file in the project's root directory place the cursor into the following source code block and press C-u C-c C-v t, this will put the code block contents into tangle-all.sh file on your disk.

./tangle.sh project.org build/*.org org/*.org

5 "Library of Babel" helper code blocks library

Helper code blocks constituting the project's "Library of Babel" are defined here. This code blocks work in Emacs/Org environment and are not supposed to be called from Clojure or ClojureScript.

5.1 time-is-now

Returns current time as a string in a given format.

Examples:

<<time-is-now()>>
<<time-is-now(format="%F")
#+CALL: time-is-now()
Table 3: Parameters:
Name Default value Description
[format] "%F %X" Time string format as supported by (format-time-string) function. Optional.
(format-time-string format)

5.2 get-table-param

Returns a value of a parameter defined in a table. Table must follow given requirements: parameter names are given in the first column, parameter values - in the second column. Hierarchical queries are supported in a way, if parameter name is given as name/subname then first name will be searched and then subname will be searched starting from name a row where name has been found.

Examples:

<<get-table-param(table=../project.org:project-specification, title="Project name")>>
<<get-table-param(table=../project.org:project-specification, title="Project name", splitby="-", joinby="_")>>
<<get-table param(table=../project.org:project-specification, title="Clojure/Version")>>
#+CALL: get-table-param(table=../project.org:project-specification, title="Project name")
Table 4: Parameters:
Name Default value Description
table (quote ()) Table reference. Reference format is [table containing org-file path:]table-name,
    for example ../file.org:table_name or just table_name if a table is given
    in the same file the call is done from. Default value simply allows return nil if
    parameter is not given, since internally code block receives a table as a list.
title "" Parameter name. Path-like parameter names are supported (i.e. names containing '/'.
    If such complex parameter is given, than it'll be splited, and each resulting part
    will be considered as a separate parameter name. The following parameter name search
    will be started from a row where the preceding parameter name has been found, a value
    of a preceding parameter is ignored. A value of the last parameter will be returned.
[splitby] "" Parameter value splitter. Optional. Works in pair with joinby. If parameter values
    a listed in a cell as comma-separated list, for example, than, giving splitby=",",
    will split'em, and splitted values will be joined with joinby value.
[joinby] "" Parameter value combinator. Optional. Works in pair with splitby.
(let* ((table-adopted (mapcar (lambda (x)
                                (if (equal x 'hline)
                                   '(hline hline hline)
                                 x))
                              table))
       (titles (mapcar #'car table-adopted))
       (path (split-string title (regexp-quote "/")))
       (last-index (reduce (lambda (index title)
                             (cl-position title titles :start index :test #'equal))
                           path
                           :initial-value nil)))
  (if last-index
      (let* ((result (nth 1 (nth last-index table-adopted))))
        (if (and result splitby joinby)
            (mapconcat #'identity (split-string result (regexp-quote splitby) t "\s+") joinby)
          result))))

5.3 get-specification-param

Returns parameter value given in the project specification table inside ../project.org file.

Examples:

<<get-specification-param(title="Project name")>>
<<get-specification-param(title="Main namespace", splitby="." joinby="/")>>
#+CALL: get-specification-param(title="Project name")
Table 5: Parameters:
Name Default value Description
title "" Parameter name (see (get-table-param)).
[splitby] "" Parameter value separator (see (get-table-param)).
[joinby] "" Parameter value combinator (see. (get-table-param)).
(save-excursion
  (let ((project-org-dir (locate-dominating-file (buffer-file-name) "project.org")))
       (if project-org-dir
         (let ((project-spec-ref (concat project-org-dir "project.org:project-specification")))
              (org-babel-execute-src-block
                nil
                (cdr (assoc 'get-table-param org-babel-library-of-babel))
                (list
                  (cons :var (format "table=%s" project-spec-ref))
                  (cons :var (format "title=\"%s\"" title))
                  (cons :var (format "splitby=\"%s\"" splitby))
                  (cons :var (format "joinby=\"%s\""  joinby))))))))

5.4 in-some-path

Returns an absolute path to a file in one of the project directories given in the project specification table, will take project name into account and insert it as intermediate directory if requested.

Requirements: Project specification must have following parameters defined:

  • Project name
  • Parameter given in param code block parameter.

Examples:

#+BEGIN_SRC clojure :tangle (org-sbe in-some-path (param="Clojure/Sources path" path \"core.clj\"))
   ...
#+END_SRC
<<in-some-path(param="Clojure/Sources path", path="core.clj")>>
#+CALL: in-some-path(param="Clojure/Sources path", path="core.clj")
Table 6: Parameters:
Name Default value Description
param "Clojure/Sources path" Parameter name defining the base directory (relative to project root)
[path] "" Path relative to built directory name
[projectvise] t Whether to insert project name between base directory path and the path
    part given in path parameter
(save-excursion
  (let* ((project-org-dir (locate-dominating-file (buffer-file-name) "project.org"))
         (src-path (org-babel-execute-src-block
                     nil
                     (cdr (assoc 'get-specification-param org-babel-library-of-babel))
                     (list
                       (cons :var (format "title=\"%s\"" param)))))
         (project-name (org-babel-execute-src-block
                         nil
                         (cdr (assoc 'get-specification-param org-babel-library-of-babel))
                         (list
                           (cons :var "title=\"Project name\"")
                           (cons :var "splitby=\".\"")
                           (cons :var "joinby=\"/\""))))
         (ns-path (replace-regexp-in-string "\-" "_" project-name)))
        (concat project-org-dir src-path "/" (if projectvise (concat ns-path "/") "") path)))

5.5 in-clj-path

Return an absolute path to a file in project's exported Clojure sources directory with respect to project name.

Requirements: Project specification must have following parameters defined:

  • Project name
  • Clojure/Sources path

Examples:

#+BEGIN_SRC clojure :tangle (org-sbe in-clj-path (path \"core.clj\"))
   ...
#+END_SRC
<<in-clj-path(path="core.clj")>>
#+CALL: in-clj-path(path="core.clj")
Table 7: Parameters:
Name Default value Description
path "" Path relative to project's exported Clojure sources directory to be appended
(save-excursion
  (org-babel-execute-src-block
    nil
    (cdr (assoc 'in-some-path org-babel-library-of-babel))
    (list
     (cons :var "param=\"Clojure/Sources path\"")
     (cons :var (format "path=\"%s\"" path)))))

5.6 in-cljs-path

Return an absolute path to a file in project's exported Clojure Script sources directory with respect to project name.

Requirements: Project specification must have following parameters defined:

  • Project name
  • ClojureScript/Sources path

Examples:

#+BEGIN_SRC clojure :tangle (org-sbe in-cljs-path (path \"core.clj\"))
   ...
#+END_SRC
<<in-cljs-path(path="core.clj")>>
#+CALL: in-cljs-path(path="core.clj")
Table 8: Parameters:
Name Default value Description
path "" Path relative to project's exported Clojure Script sources directory to be appended
(save-excursion
  (org-babel-execute-src-block
    nil
    (cdr (assoc 'in-some-path org-babel-library-of-babel))
    (list
     (cons :var "param=\"ClojureScript/Sources path\"")
     (cons :var (format "path=\"%s\"" path)))))

5.7 in-tests-path

Return an absolute path to a file in project's exported tests sources directory with respect to project name.

Requirements: Project specification must have following parameters defined:

  • Project name
  • Tests path

Examples:

#+BEGIN_SRC clojure :tangle (org-sbe in-tests-path (path \"core.clj\"))
   ...
#+END_SRC
<<in-tests-path(path="core.clj")>>
#+CALL: in-tests-path(path="core.clj")
Table 9: Parameters:
Name Default value Description
path "" Path relative to project's exported tests directory to be appended
(save-excursion
  (org-babel-execute-src-block
    nil
    (cdr (assoc 'in-some-path org-babel-library-of-babel))
    (list
     (cons :var "param=\"Tests path\"")
     (cons :var (format "path=\"%s\"" path)))))

5.8 in-resources-path

Return an absolute path to a file in project's exported resources directory.

Requirements: Project specification must have following parameters defined:

  • Project name
  • Resource path

Examples:

#+BEGIN_SRC clojure :tangle (org-sbe in-resources-path (path \"index.html"))
   ...
#+END_SRC
<<in-resources-path(path="index.html")>>
#+CALL: in-resources-path(path="index.html")
Table 10: Parameters:
Name Default value Description
path "" A path relative to project's exported resources directory to be appended
(save-excursion
  (org-babel-execute-src-block
    nil
    (cdr (assoc 'in-some-path org-babel-library-of-babel))
    (list
     (cons :var "param=\"Resource path\"")
     (cons :var (format "path=\"%s\"" path))
     (cons :var "projectvise=()"))))

5.9 in-assets-path

Return an absolute path to a file in project's exported assets directory.

Requirements: Project specification must have following parameters defined:

  • Project name
  • Assets path

Examples:

#+BEGIN_SRC clojure :tangle (org-sbe in-resources-path (path \"index.html"))
   ...
#+END_SRC
<<in-resources-path(path="index.html")>>
#+CALL: in-resources-path(path="index.html")
Table 11: Parameters:
Name Default value Description
path "" A path relative to project's exported assets directory to be appended
(save-excursion
  (org-babel-execute-src-block
    nil
    (cdr (assoc 'in-some-path org-babel-library-of-babel))
    (list
     (cons :var "param=\"Assets path\"")
     (cons :var (format "path=\"%s\"" path))
     (cons :var "projectvise=()"))))

5.10 in-target-path

Returns an absolute path to a file in project's target directory.

Requirements: Project specification must have following parameters defined:

  • Project name
  • Target path

Examples:

#+BEGIN_SRC clojure :tangle (org-sbe in-target-path (path \"project.jar"))
   ...
#+END_SRC
<<in-target-path(path="project.jar")>>
#+CALL: in-target-path(path="project.jar")
Table 12: Parameters:
Name Default value Description
path "" A path relative to project's target directory to be appended
(save-excursion
  (org-babel-execute-src-block
    nil
    (cdr (assoc 'in-some-path org-babel-library-of-babel))
    (list
     (cons :var "param=\"Target path\"")
     (cons :var (format "path=\"%s\"" path))
     (cons :var "projectvise=()"))))

5.11 render-project-dependencies

Renders (returns as a string) project dependencies, defined in the project dependencies table, as a list of Clojure vectors.

Examples:

<<render-project-dependencies()>>
#+CALL: render-project-dependencies()
(org-sbe render-project-dependencies)
; Full dependency definition specification is given here
; https://github.com/cemerick/pomegranate/blob/master/src/main/clojure/cemerick/pomegranate/aether.clj
; in resolve-dependencies function
(require 'subr-x)
(require 'seq)

(let ((project-org-dir (locate-dominating-file (buffer-file-name) "project.org")))
  (if project-org-dir
      (let* ((project-spec-ref (concat project-org-dir "project.org:project-dependencies"))
             ; deps-table is a list of lists and hlines
             (deps-table (org-babel-ref-resolve project-spec-ref))
             ; Dependency representing hash-map key traversing sequence
             (serialize-key-traversing-seq '(name version scope optional classifier extension exclusions)))
        (cl-labels (
                  ; dependency hash map has following keys
                  ; - name - dependency artifact name
                  ; - version - dependency artifact version
                  ; - scope - dependency scope
                  ; - optional - flag showing whether a dependency is optional, any value but "" and "no" is considered to be true
                  ; - classifier - dependency Maven-classifier
                  ; - extension - dependency Maven-extension
                  ; - exclusions - list of transient dependency exclusions for a dependency
                  (make-dependency (name version scope optional classifier extension exclusions)
                                   (let ((new-dep (make-hash-table)))
                                     (puthash 'name name new-dep)
                                     (puthash 'version (format "%s" version) new-dep)
                                     (puthash 'scope scope new-dep)
                                     (puthash 'optional (if (or
                                                             (string-empty-p optional)
                                                             (string= "no" (downcase optional)))
                                                            nil
                                                          't)
                                              new-dep)
                                     (puthash 'classifier classifier new-dep)
                                     (puthash 'extension  extension  new-dep)
                                     (puthash 'exclusions exclusions new-dep)
                                     new-dep))
                  ; Dependency serializing function
                  (serialize-dependency (dependency)
                                        (concat "["
                                                ; Traversing every key in the dependency hash table and building dependency definition string
                                                (mapconcat (lambda (key)
                                                             (let ((value (gethash key dependency)))
                                                                  (cond
                                                                    ; name is always given
                                                                    ((equal key 'name)
                                                                        value)
                                                                          ; I'm not sure but maybe version might be empty
                                                                    ((and (equal key 'version) (not (string-empty-p value)))
                                                                        (format "\"%s\"" value))
                                                                          ; Scope is optional
                                                                    ((and (equal key 'scope) (not (string-empty-p value)))
                                                                        (format ":scope \"%s\"" value))
                                                                          ; Optional flag should be set only if it's true
                                                                    ((and (equal key 'optional) value)
                                                                        ":optional true")
                                                                          ; Classifier is optinal
                                                                    ((and (equal key 'classifier) (not (string-empty-p value)))
                                                                        (format ":classifier \"%s\"" value))
                                                                          ; Extension is optional
                                                                    ((and (equal key 'extension) (not (string-empty-p value)))
                                                                        (format ":extension \"%s\"" value))
                                                                          ; Exclusions should be given if there're any
                                                                    ((and (equal key 'exclusions) (not (seq-empty-p value)))
                                                                        (concat
                                                                            ":exclusions ["
                                                                            (mapconcat #'serialize-dependency value " ")
                                                                            "]")))))
                                                           serialize-key-traversing-seq
                                                           " ")
                                                "]")))
          (let (
                ; deps-adopted is a list of hash tables representing dependencies
                (deps-adopted (reduce
                                (lambda (deps-adopted dependency)
                                  ; Skip any hlines and empty rows
                                  (if (or (equal dependency 'hline) (every #'string-empty-p dependency))
                                      deps-adopted
                                    ; Else destructuring the dependency given
                                    (destructuring-bind (name version scope optional classifier extension exclusions ex-classifier ex-extension _) dependency
                                      (cond
                                       ; Append new dependency hash map in case there's a name given
                                       ((not (string-empty-p name))
                                        (append deps-adopted
                                                (list (make-dependency name
                                                                       version
                                                                       scope
                                                                       optional
                                                                       classifier
                                                                       extension
                                                                       (if (not (string-empty-p exclusions))
                                                                           (list (make-dependency exclusions
                                                                                                  ""
                                                                                                  ""
                                                                                                  ""
                                                                                                  ex-classifier
                                                                                                  ex-extension
                                                                                                  nil)))))))
                                       ; Append another exclusion in case there's no name but exclusions given
                                       ((and (string-empty-p name) (not (string-empty-p exclusions)))
                                        (let* ((last-dep (car (last deps-adopted))))
                                          (puthash 'exclusions
                                                   (append (gethash 'exclusions last-dep nil)
                                                           (list (make-dependency exclusions
                                                                                  ""
                                                                                  ""
                                                                                  ""
                                                                                  ex-classifier
                                                                                  ex-extension
                                                                                  nil)))
                                                   last-dep)
                                          deps-adopted))))))
                                ; Skipping title line
                                (cdr deps-table)
                                :initial-value nil)))
            ; So now I have list of dependencies (as hash maps) which I'm to transform into Clojure's vector of vectors
            ; in Clojure syntax
            (mapconcat #'serialize-dependency deps-adopted "\n"))))))

5.12 render-code-block-if

Renders a code block if the project specification table contains a parameter with a given name and a value passing an equality condition.

Requirements:

  • A code block with a given name must be defined in file the call is done from.

Examples:

<<render-codeblock-if(name="project-clojurescript-dependencies" if="ClojureScript=yes")>>
#+CALL: render-codeblock-if(name="project-clojurescript-dependencies" if="ClojureScript=yes")
Table 13: Parameters:
Name Default value Description
name "" A code block name to be rendered, the one given in it's #+NAME: dirrective.
condition "" Rendering condition given as "parameter = value", where = is string comparison
    operation.
(save-excursion
  (destructuring-bind (&optional (param-name "") &optional (cond-value "")) (split-string condition "=" t "\s+")
    (let ((param-value (org-babel-execute-src-block
                        nil
                        (cdr (assoc 'get-specification-param org-babel-library-of-babel))
                        (list
                         (cons :var (format "title=\"%s\"" param-name))))))
      (if (string= param-value cond-value)
          (progn
            (org-babel-goto-named-src-block name)
            (let ((tangle-result (org-babel-tangle-single-block "")))
              (nth 5 tangle-result)))
        ""))))

6 Boot-project

6.1 boot.properties

boot.properties file is used by Boot to link your Clojure/ClojureScript project to a particular version of Boot executing by a particular version of Clojure.

#http://boot-clj.com
#2016-12-19 07:29:34
BOOT_CLOJURE_NAME=org.clojure/clojure
BOOT_CLOJURE_VERSION=1.8.0
BOOT_VERSION=2.6.0

6.2 build.boot

build.boot file is executed by Boot upon running the following command boot task, where task is a name of Boot-task to execute. Project's tasks are defined below in a corresponding sections.

Setting up corresponding project directories if they are defined in the project specification table and exist on build machine disk.

(def paths [ [:source-paths "src"]
             [:source-paths "src"]
             [:resource-paths "resources"]
             [:asset-paths "assets"] ] )

(doseq [[ env-key path ] paths]
  (if (and (seq path) (not ((get-env env-key) path)))
    (let [dir (file path)]
      (if (.isDirectory dir)
        (set-env! env-key #(conj % (.getName dir)))))))

If dependency from Clojure is defined in the project specification table, i.e. value yes is given against parameter Clojure, than adding org.clojure/clojure dependency.

(set-env! :dependencies #(conj % '[org.clojure/clojure "1.8.0"]))

If dependency from ClojureScript is defined in the project specification table, i.e. value yes is given against parameter ClojureScript, than adding org.clojure/clojurescript dependency.

(set-env! :dependencies #(conj % '[org.clojure/clojurescript "1.9.225"]))

Adding project dependencies defined in the project dependencies table.

(let [deps '[

           ]]
  (set-env! :dependencies #(into % deps)))

6.2.1 Clojure related project's Boot-tasks

These tasks will be added into resulting build.boot file, if Clojure parameter is set to yes in the project specification table

All blocks of code defining Boot-tasks for Clojure must have boot-clojure-task name. Each task defined in code block with boot-clojure-task name will be added into resulting build.boot file.

6.2.1.1 clj-build

This task builds project's uberjar which might be run with java -jar target/project.jar

(deftask clj-build
  "Builds project's uberjar"
  []
  (comp (pom :project 'literate-boot
             :version "0.1.0-SNAPSHOT")
        (aot :all true)
        (uber)
        (jar :main 'literate-boot.core)
        (target :dir #{"target"})))

To execute the task run: boot clj-build

6.2.1.2 clj-run

This task executes project's main function in the Boot's Java environment.

(deftask clj-run
  "Runs the project's main function"
  []
  (require 'literate-boot.core)
  (let [main-fn (resolve 'literate-boot.core/-main)]
    (comp (aot :all true) (with-pass-thru _ (main-fn)))))

To execute the task run: boot clj-run

6.2.2 ClojureScript related project's Boot-задачи

These tasks will be added into resulting build.boot file, if ClojureScript parameter is set to yes in the project specification table

All blocks of code defining Boot-tasks for Clojure must have boot-clojurescript-task name. Each task defined in a code block with boot-clojurescript-task name will be added into resulting build.boot file.

6.2.2.1 cljs-figwheel

This task is an interface to figwheel, to execute it run boot cljs-figwheel.

The task compiles ClojureScript files, runs HTTP-server serving project's public resources directory, runs changes monitor and automaticaly recompiles and reloads the project upon *.cljs files change, runs nRepl server.

To run a ClojureScript repl one has to go through "9 steps to success":

  1. run boot cljs-figwheel
  2. wait for Clojure repl prompt
  3. look for address and port of a run web-server (usually http://localhost:3449/) in the console output
  4. open a browser, novigate to web-server address:port URL, open browser's developer tools console and make sure there's "Opened Websocket REPL connection" message present
  5. look address and port of a nRepl server, in the console output and pass it to (cider-connect) call at the next step
  6. execute (cider-connect HOST PORT) or M-x cider-connect and input nRepl HOST and PORT, though, it appears as if cider is able to take the values needed from .nrepl-port file, so one can just press ENTER twice
  7. execute (cljs-repl) in repl-buffer to run a ClojureScript REPL
  8. execute (.log js/console "OK!") in repl-buffer and make sure there's "OK!" message present in the brower's developer tools console.
  9. done!
(set-env! :dependencies #(into % '[[ajchemist/boot-figwheel "0.5.4-6" :scope "test"]
                                   [org.clojure/tools.nrepl "0.2.12" :scope "test"]
                                   [com.cemerick/piggieback "0.2.1" :scope "test"]
                                   [figwheel-sidecar "0.5.7" :scope "test"]]))

(require '[boot-figwheel :refer [figwheel start-figwheel! stop-figwheel! start-autobuild stop-autobuild cljs-repl fig-status]])

;; This is for (cider-connect) to work.
;; NOTE: do not use (cider-jack-in), use (cider-connect) instead
;; ---------------------------
;; Start of cider related code
(swap! boot.repl/*default-dependencies* concat '[[cider/cider-nrepl "0.14.0"]])
(swap! boot.repl/*default-middleware* conj 'cider.nrepl/cider-middleware)
;; End of cider related code

(deftask cljs-figwheel
  "Compiles ClojureScript, serves it, autobuilds"
  []
  (comp
   (figwheel :build-ids ["dev"]
             ;:once-build ["advanced"]
             :all-builds [{:id "dev"
                           :source-paths (seq (get-env :source-paths))
                           :compiler {:main 'literate-boot.core
                                      :optimizations :none
                                      :source-map true
                                      :output-to "app.js"}
                           :figwheel {:build-id "dev"
                                      :on-jsload 'literate-boot.core/-main
                                      :heads-up-display true
                                      :autoload true
                                      :debug false}}]
             :figwheel-options {:http-server-root "public"
                                :open-file-command "emacsclient"
                                :repl false}
             :target-path "resources/public")
   (speak)
   (repl)))

6.2.3 User's Boot-tasks

All code blocks defining user's Boot-tasks must have boot-user-task name. Each tasks defined in a code block with boot-user-task name will be included into resulting build.boot file.

7 Project code

7.1 Core namespace

7.1.1 Functions

7.1.1.1 -main
(ns literate-boot.core
  (:gen-class))

(defn -main
  "Project's entry point"
  [& args]
  (println "Hello, from literate-boot!"))
(ns literate-boot.core
  (:refer-clojure))

(enable-console-print!)

(defn ^:export -main
  "Project's entry point"
  [& args]
  (println "Hello, from literate-boot!!"))
 <!doctype html>
 <html>
  <head>
   <title>literate-boot</title>
  </head>
  <body>
    <script src="app.js"></script>
  </body>
</html>

Footnotes:

1

For the one interested please see the documentation for cemeric.pomegranate.aether/resolve-dependencies function

2

tangle.sh and tangle-all.sh shell-script has been taken from thi.ng/babel project and adopted to this project a bit.

Author: Maxim Bazhenov

Email: maximgb@yandex.ru

Created: 2016-12-19 Пн. 07:29

Validate