Saturday, 14 April 2018

Live reload ClojureScript & JavaScript during Cordova app development

Using ClojureScript for Cordova app development is described in a previous post. This post will expand on how to do live reloading when code changes and also writing JavaScript unit tests with QUnit. The a sample code structure is given below.
example-cordova-app
├── Gruntfile.js
├── config.xml
├── hooks
├── node_modules
├── package-lock.json
├── package.json
├── platforms
│   └── browser
│       ├── browser.json
│       ├── ...
├── plugins
├── res
├── example-cordova-cljs
│   ├── out
│   ├── project.clj
│   ├── resources
│   ├── src
│   │   └── my_app
│   │       └── core.cljs
│   ├── target
│   └── test
│       └── my_app
│           └── core_test.clj
└── www
    ├── css
    │   └── style.css
    ├── img
    ├── index.html
    ├── js
    │   ├── app.js
    │   ├── libs
    │   ├── main.js
    └── test
        ├── qunit.css
        ├── qunit.js
        ├── test.html
        └── tests.js
We can use browser platform to do quick testing during development and cordova-plugin-browsersync to do live reloading of www folder.
1. Install cordova-plugin-browsersync.
cordova plugins add cordova-plugin-browsersync
2. Once the plugin is installed, we can start the watcher from terminal.
cordova run browser -- --live-reload
When ClojureScript changes, it compiles and places the file into www, and the above plugin will detect the change and do a reload. Refresh the browser and the latest code changes will be reflected. This is also useful when we mix JavaScript and ClojureScript.

Unit Testing with QUnit
We can write ClojureScript test, which is a different workflow. Place test scripts under www/test folder. A sample test.html is shown below.
<!doctype html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="qunit.css">
    <script type="text/javascript" src="qunit.js"></script>
    <script type="text/javascript" src="tests.js"></script>
    <title>Testsuite</title>
</head>
<body>
    <div id="qunit"></div>
    <div id="qunit-fixture"></div>
</body>
</html>
Test scripts can go to test.js.
// test.js
if (document.loaded) {
    test();
} else {
    window.addEventListener('load', test, false);
}

function test() {
    QUnit.module("test");
    QUnit.test("Example", function (assert) {
        assert.ok(true, "ok is for boolean test");
        assert.equal(1, "1", "comparison");
    });
}
We will use grunt to run these tasks. Add both live reload and unit test tasks in Gruntfile.js.
module.exports = function(grunt) {
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        qunit: {
            files: ['www/test/**/*.html']
        },
        exec: {
            start: {
                command: 'cordova run browser -- --live-reload'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-qunit');
    grunt.loadNpmTasks('grunt-exec');

    grunt.registerTask('test', ['qunit']);
    grunt.registerTask('start', ['exec:start']);
};
The required dependencies added to package.json follows.
{
    "name": "com.qlambda.example.app",
    // ...
    "main": "main.js",
    "scripts": {
        "start": "cordova run browser -- --live-reload",
        "test": "grunt test"
    },
    "dependencies": {
        "browser-sync": "^2.23.6",
        "cordova-browser": "^5.0.3",
        "cordova-plugin-browsersync": "^1.1.0",
        "cordova-plugin-whitelist": "^1.3.3",
        // ...
    },
    "cordova": {
        "plugins": {
            "cordova-plugin-whitelist": {},
            "cordova-plugin-browsersync": {}
        },
        "platforms": [
            "browser"
        ]
    },
    "devDependencies": {
        "grunt": "^1.0.2",
        "grunt-contrib-qunit": "^2.0.0",
        "grunt-exec": "^3.0.0"
    }
}
The tasks start, test are available as grunt task and we can link main ones to npm as well.
# grunt
grunt start  # start watcher
grunt test   # run testsuite

# npm
npm start
npm test

Sunday, 8 April 2018

macOS Server is a disappointment

macOS Server is very much a disappointment. The main reason for me to use it is because of the quick setup of Calendar, Contact, Notes, DNS and wiki (which I stopped using) which syncs with rest of the Apple devices when within the internal network, without having to install, configure and fiddle with these services separately. With each new update of the server app, Apple keeps removing features, which begs the question, is it going to be discontinued, which very much likely is.


With the latest update 5.6 more services are being removed to prepare for the migration to alternate services. The support article HT208312 lists the following services to be removed in fall 2018.
DHCP, DNS, VPN, Firewall, Mail Server, Calendar, Wiki, Websites, Contacts, Net Boot/Net Install, Messages, Radius, Airport Management
Apple recommends to install those services separately rendering the mac Server app useless (to me).

Thursday, 5 April 2018

Simple cowsay in Clojure

A simple version of cowsay in Clojure.
(ns fortune
  "Fortune fairy."
  (:require [clojure.pprint :as pprn]
            [clojure.string :as str])
  (:import [java.util.concurrent ThreadLocalRandom]))

(def tale ["I'll walk where my own nature would be leading: It vexes me to choose another guide."
           "Every leaf speaks bliss to me, fluttering from the autumn tree."
           "I see heaven's glories shine and faith shines equal."
           "I have to remind myself to breathe -- almost to remind my heart to beat!"
           "I’ve dreamt in my life dreams that have stayed with me ever after, and changed my ideas: they’ve gone through and through me, like wine through water, and altered the colour of my mind."])

(defn gen-random [lb ub]
  (-> (ThreadLocalRandom/current)
      (.nextInt lb ub)))

(defn gen-rand-txt []
  (nth tale (gen-random 0 (count tale))))

(def cowsay-body
"        \\   ^__^
         \\  (oo)\\_______
            (__)\\       )\\/\\
                ||----w |
                ||     ||")

(defn cowsay-hr [width]
  (println (pprn/cl-format nil "+ ~v@<~d~> +" width (apply str (repeat width "-")))))

(defn cowsay-txt-fmt [msg width]
  (println (pprn/cl-format nil "| ~v@<~d~> |" width (str/trim msg))))

(defn cowsay
  ([]
    (let [txt (gen-rand-txt)]
      (cowsay txt 0 21 21 (count txt))))
  ([msg]
    (let [txt (if (empty? msg) (gen-rand-txt) msg)]
      (cowsay txt 0 21 21 (count txt))))
  ([msg width]
    (cowsay msg 0 width width (count msg)))
  ([msg start end width len]
    (when (= start 0) (cowsay-hr width))
    (cond
      (<= end len) (do
                    (cowsay-txt-fmt (subs msg start end) width)
                    (recur msg (+ start width) (+ end width) width len))
      (< start end) (do
                      (cowsay-txt-fmt (subs msg start len) width)
                      (cowsay-hr width)                  
                      (println cowsay-body)))))
The format specifiers in cl-format is very expressive. Since this is a simple version, it just left aligns by characters.
boot.user=> (load-file "fortune.clj")
#'fortune/cowsay

boot.user=> (require '[fortune :as fortune])
nil

boot.user=> (fortune/cowsay)
+ --------------------- +
| I see heaven's glorie |
| s shine and faith shi |
| nes equal.            |
+ --------------------- +
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
nil

boot.user=> (f/cowsay (first f/tale) 0 13 13 (count (first f/tale)))
+ ------------- +
| I'll walk whe |
| re my own nat |
| ure would be  |
| leading: It v |
| exes me to ch |
| oose another  |
| guide.        |
+ ------------- +
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
nil

Boot Task
We can further use this as a boot task. Let's say we placed this in scripts under the project root and we add the below snippet to build.boot.
(def generic-pod (future (pod/make-pod (core/get-env))))

(deftask cowsay
  [m msg VAL str "Message to print"]
  (merge-env! :source-paths #{"scripts"})
  (pod/with-call-in @generic-pod (fortune/cowsay ~msg))
  identity)
This can be run as boot cowsay -m "Every leaf speaks bliss to me, fluttering from the autumn tree.".