diff --git a/README.md b/README.md
index ddb5c04..2989373 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,7 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
module | branch | status
----------|----------|----------
mount | `master` | [](https://circleci.com/gh/tolitius/mount/tree/master)
+ mount | `0.1.5` | [](https://circleci.com/gh/tolitius/mount/tree/0.1.5)
[](http://clojars.org/mount)
@@ -18,6 +19,7 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
- [Differences from Component](#differences-from-component)
- [How](#how)
- [Creating State](#creating-state)
+ - [Value of Values](#value-of-values)
- [Using State](#using-state)
- [Dependencies](#dependencies)
- [Talking States](#talking-states)
@@ -25,7 +27,14 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
- [Start and Stop Order](#start-and-stop-order)
- [Start and Stop Parts of Application](#start-and-stop-parts-of-application)
- [Start an Application Without Certain States](#start-an-application-without-certain-states)
+- [Stop an Application Except Certain States](#stop-an-application-except-certain-states)
- [Swapping Alternate Implementations](#swapping-alternate-implementations)
+- [Suspending and Resuming](#suspending-and-resuming)
+ - [Suspendable Lifecycle](#suspendable-lifecycle)
+ - [Plugging into (reset)](#plugging-into-reset)
+ - [Suspendable Example Application](#suspendable-example-application)
+- [Affected States](#affected-states)
+- [Logging](#logging)
- [Mount and Develop!](#mount-and-develop)
- [Running New York Stock Exchange](#running-new-york-stock-exchange)
- [Web and Uberjar](#web-and-uberjar)
@@ -72,15 +81,15 @@ mount is an alternative to the [component](https://github.com/stuartsierra/compo
Creating state is easy:
```clojure
-(defstate conn :start (create-conn))
+(defstate conn :start create-conn)
```
-where `(create-conn)` is defined elsewhere, can be right above it.
+where the `create-conn` function is defined elsewhere, can be right above it.
In case this state needs to be cleaned / destryed between reloads, there is also `:stop`
```clojure
-(defstate conn :start (create-conn)
+(defstate conn :start create-conn
:stop (disconnect conn))
```
@@ -94,6 +103,14 @@ dev=> conn
#object[datomic.peer.LocalConnection 0x1661a4eb "datomic.peer.LocalConnection@1661a4eb"]
```
+#### Value of values
+
+Lifecycle functions start/stop/suspend/resume can take both functions and values. This is "valuable" and also works:
+
+```clojure
+(mount/defstate answer-to-the-ultimate-question-of-life-the-universe-and-everything :start 42)
+```
+
### Using State
For example let's say an `app` needs a connection above. No problem:
@@ -248,10 +265,157 @@ One thing to note, whenever
(mount/stop)
```
-is run after `start-with`, it rolls back to an original "state of states", i.e. `#'app.nyse/db` is `#'app.nyse/db` again. So a subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
+is run after `start-with`, it rolls back to an original "state of states", i.e. `#'app.nyse/db` is `#'app.nyse/db` again. So subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
Here is an [example](test/check/start_with_test.clj) test that starts an app with mocking Datomic connection and nREPL.
+## Stop an Application Except Certain States
+
+Calling `(mount/stop)` will stop all the application states. In case everything needs to be stopped _besides certain ones_, it can be done with `(mount/stop-except)`.
+
+Here is an example of restarting the application without bringing down `#'app.www/nyse-app`:
+
+```clojure
+dev=> (mount/start)
+14:34:10.813 [nREPL-worker-0] INFO mount.core - >> starting.. app-config
+14:34:10.814 [nREPL-worker-0] INFO mount.core - >> starting.. conn
+14:34:10.814 [nREPL-worker-0] INFO app.db - creating a connection to datomic: datomic:mem://mount
+14:34:10.838 [nREPL-worker-0] INFO mount.core - >> starting.. nyse-app
+14:34:10.843 [nREPL-worker-0] DEBUG o.e.j.u.component.AbstractLifeCycle - STARTED SelectChannelConnector@0.0.0.0:4242
+14:34:10.843 [nREPL-worker-0] DEBUG o.e.j.u.component.AbstractLifeCycle - STARTED org.eclipse.jetty.server.Server@194f37af
+14:34:10.844 [nREPL-worker-0] INFO mount.core - >> starting.. nrepl
+:started
+
+dev=> (mount/stop-except #'app.www/nyse-app)
+14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. nrepl
+14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. conn
+14:34:47.766 [nREPL-worker-0] INFO app.db - disconnecting from datomic:mem://mount
+14:34:47.766 [nREPL-worker-0] INFO mount.core - << stopping.. app-config
+:stopped
+dev=>
+
+dev=> (mount/start)
+14:34:58.673 [nREPL-worker-0] INFO mount.core - >> starting.. app-config
+14:34:58.674 [nREPL-worker-0] INFO app.config - loading config from test/resources/config.edn
+14:34:58.674 [nREPL-worker-0] INFO mount.core - >> starting.. conn
+14:34:58.674 [nREPL-worker-0] INFO app.db - creating a connection to datomic: datomic:mem://mount
+14:34:58.693 [nREPL-worker-0] INFO mount.core - >> starting.. nrepl
+:started
+```
+
+Notice that the `nyse-app` is not started the second time (hence no more accidental `java.net.BindException: Address already in use`). It is already up and running.
+
+## Suspending and Resuming
+
+Besides starting and stopping states can also be suspended and resumed. While this is not needed most of the time, it does comes really handy _when_ this need is there. For example:
+
+* while working in REPL, you only want to truly restart a web server/queue listener/db connection _iff_ something changed, all other times `(mount/stop)` / `(mount/start)` or `(reset)` is called, these states should not be restarted. This might have to do with time to connect / bound ports / connection timeouts, etc..
+
+* when taking an application out of rotation in a data center, and then phasing it back in, it might be handy to still keep it _up_, but suspend all the client / novelty facing components in between.
+
+and some other use cases.
+
+### Suspendable Lifecycle
+
+In additiong to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones:
+
+```clojure
+(defstate web-server :start start-server
+ :resume resume-server
+ :stop stop-server)
+
+```
+
+`suspend` function is optional. Combining this with [(mount/stop-except)](#stop-an-application-except-certain-states), can result in an interesting restart behavior where everything is restared, but this `web-server` is _resumed_ instead (in this case `#'app.www/nyse-app` is an example of the above `web-server`):
+
+```clojure
+dev=> (mount/stop-except #'app.www/nyse-app)
+14:44:33.991 [nREPL-worker-1] INFO mount.core - << stopping.. nrepl
+14:44:33.992 [nREPL-worker-1] INFO mount.core - << stopping.. conn
+14:44:33.992 [nREPL-worker-1] INFO app.db - disconnecting from datomic:mem://mount
+14:44:33.992 [nREPL-worker-1] INFO mount.core - << stopping.. app-config
+:stopped
+dev=>
+
+dev=> (mount/suspend)
+14:44:52.467 [nREPL-worker-1] INFO mount.core - >> suspending.. nyse-app
+:suspended
+dev=>
+
+dev=> (mount/start)
+14:45:00.297 [nREPL-worker-1] INFO mount.core - >> starting.. app-config
+14:45:00.297 [nREPL-worker-1] INFO mount.core - >> starting.. conn
+14:45:00.298 [nREPL-worker-1] INFO app.db - creating a connection to datomic: datomic:mem://mount
+14:45:00.315 [nREPL-worker-1] INFO mount.core - >> resuming.. nyse-app
+14:45:00.316 [nREPL-worker-1] INFO mount.core - >> starting.. nrepl
+:started
+```
+
+Notice `>> resuming.. nyse-app`, which in [this case](https://github.com/tolitius/mount/blob/suspendable/test/app/www.clj#L32) just recreates Datomic schema vs. doing that _and_ starting the actual web server.
+
+### Plugging into (reset)
+
+In case `tools.namespace` is used, this lifecycle can be easily hooked up with `dev.clj`:
+
+```clojure
+(defn start []
+ (mount/start))
+
+(defn stop []
+ (mount/suspend)
+ (mount/stop-except #'app.www/nyse-app))
+
+(defn reset []
+ (stop)
+ (tn/refresh :after 'dev/start))
+```
+
+### Suspendable Example Application
+
+An [example application](https://github.com/tolitius/mount/tree/suspendable/test/app) with a suspendable web server and `dev.clj` lives in the `suspendable` branch. You can clone mount and try it out:
+
+```
+$ git checkout suspendable
+Switched to branch 'suspendable'
+```
+
+## Affected States
+
+Every time a lifecycle function (start/stop/suspend/resume) is called mount will return all the states that were affected:
+
+```clojure
+dev=> (mount/start)
+{:started [#'app.config/app-config
+ #'app.nyse/conn
+ #'app/nrepl
+ #'check.suspend-resume-test/web-server
+ #'check.suspend-resume-test/q-listener]}
+```
+```clojure
+dev=> (mount/suspend)
+{:suspended [#'check.suspend-resume-test/web-server
+ #'check.suspend-resume-test/q-listener]}
+```
+```clojure
+dev=> (mount/start)
+{:started [#'check.suspend-resume-test/web-server
+ #'check.suspend-resume-test/q-listener]}
+```
+
+An interesting bit here is a vector vs. a set: all the states are returned _in the order they were changed_.
+
+## Logging
+
+> All the mount examples have `>> starting..` / `<< stopping..` logging messages, but when I develop an application with mount I don't see them.
+
+Valid question. It was a [conscious choice](https://github.com/tolitius/mount/issues/15) not to depend on any particular logging library, since there are few to select from, and this decision is best left to the developer who may choose to use mount.
+
+Since mount is a _library_ it should _not_ bring any dependencies unless its functionality directly depends on them.
+
+> But I still these logging statements in the examples.
+
+The way this is done is via an excellent [robert hooke](https://github.com/technomancy/robert-hooke/). Example applications live in `test`, so does the [utility](https://github.com/tolitius/mount/blob/75d7cdc610ce38623d4d3aea1da3170d1c9a3b4b/test/app/utils/logging.clj#L44) that adds logging to all the mount's lifecycle functions on start in [dev.clj](https://github.com/tolitius/mount/blob/75d7cdc610ce38623d4d3aea1da3170d1c9a3b4b/dev/dev.clj#L21).
+
## Mount and Develop!
`mount` comes with an example [app](https://github.com/tolitius/mount/tree/master/test/app)
@@ -366,6 +530,7 @@ Switched to branch 'with-args'
```
The documentation is [here](doc/runtime-arguments.md#passing-runtime-arguments).
+
## License
Copyright © 2015 tolitius
diff --git a/dev/dev.clj b/dev/dev.clj
index 76c58d1..9894e54 100644
--- a/dev/dev.clj
+++ b/dev/dev.clj
@@ -1,8 +1,6 @@
(ns dev
"Tools for interactive development with the REPL. This file should
- not be included in a production build of the application."
- ;; (:use [cljs.repl :only [repl]]
- ;; [cljs.repl.browser :only [repl-env]])
+ not be included in a production build of the application."
(:require [clojure.java.io :as io]
[clojure.java.javadoc :refer [javadoc]]
[clojure.pprint :refer [pprint]]
@@ -11,8 +9,8 @@
[clojure.set :as set]
[clojure.string :as str]
[clojure.test :as test]
- ;; [clojure.core.async :refer [>!! ! _[source](http://www.javacodegeeks.com/2015/09/clojure-web-development-state-of-the-art.html):_
-
-> _I think all agreed that Component is the industry standard for managing lifecycle of Clojure applications. If you are a Java developer you may think of it as a Spring (DI) replacement – you declare dependencies between “components” which are resolved on “system” startup. So you just say “my component needs a repository/database pool” and component library “injects” it for you._
-
-While this is a common understanding, the Component is far from being Spring, in a good sense:
-
-* its codebase is fairly small
-* it aims to solve one thing and one thing only: manage application state via inversion of control
-
-The not so hidden benefit is REPL time reloadability that it brings to the table with `component/start` and `component/stop`
-
-
-**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
-
-- [Then why "mount"!?](#then-why-mount)
-- [So what are the differences?](#so-what-are-the-differences)
- - [Objects vs. Namespaces](#objects-vs-namespaces)
- - [Start and Stop Order](#start-and-stop-order)
- - [Component requires whole app buy in](#component-requires-whole-app-buy-in)
- - [Refactoring an existing application](#refactoring-an-existing-application)
- - [Code navigation](#code-navigation)
- - [Starting and stopping _parts_ of an application](#starting-and-stopping-_parts_-of-an-application)
- - [Boilerplate code](#boilerplate-code)
-- [What Component does better](#what-component-does-better)
- - [Swapping alternate implementations](#swapping-alternate-implementations)
- - [Uberjar / Packaging](#uberjar--packaging)
- - [Multiple separate systems](#multiple-separate-systems)
- - [Visualizing dependency graph](#visualizing-dependency-graph)
-
-
-
-## Then why "mount"!?
-
-[mount](https://github.com/tolitius/mount) was created after using Component for several projects.
-
-While Component is an interesting way to manage state, it has its limitations that prevented us
-from having the ultimate super power of Clojure: _fun working with it_. Plus several other disadvantages
-that we wanted to "fix".
-
-## So what are the differences?
-
-### Objects vs. Namespaces
-
-One thing that feels a bit "unClojure" about Component is "Objects". Objects everywhere, and Objects for everything.
-This is how Component "separates explicit dependencies" and "clears the bounaries".
-
-This is also how an Object Oriented language does it, which does not leave a lot of room for functions:
-with Component most of the functions are _methods_ which is an important distinction.
-
-Mount relies on Clojure namespaces to clear the boundaries. No change from Clojure here: `defstate` in one namespace
-can be easily `:require`d in another.
-
-### Start and Stop Order
-
-Component relies on a cool [dependency](https://github.com/stuartsierra/dependency) library to build
-a graph of dependencies, and start/stop them via topological sort based on the dependencies in this graph.
-
-Since Mount relies on Clojure namespaces and `:require`/`:use`, the order of states
-and their dependencies are revealed by the Clojure Compiler itself. Mount just records that order and replays
-it back and forth on stop and start.
-
-### Component requires whole app buy in
-
-Component really only works if you build your entire app around its model: application is fully based on Components
-where every Component is an Object.
-
-Mount does not require you to "buy anything at all", it is free :) Just create a `defstate` whenever/whereever
-you need it and use it.
-
-This one was a big deal for all the projects we used Component with, "the whole app buy in" converts an "_open_" application
-of Namespaces and Functions to a "_closed_" application of Objects and Methods. "open" and "close"
-here are rather feelings, but it is way easier and more natural to
-
-* go to a namespace to see this function
-than to
-* go to a namespace, go to a component, go to another component that this function maybe using/referenced at via a component key, to get the full view of the function.
-
-Again this is mostly a personal preference: the code works in both cases.
-
-### Refactoring an existing application
-
-Since to get the most benefits of Component the approach is "all or nothing", to rewrite an existing application
-in Component, depending on the application size, is daunting at best.
-
-Mount allows adding `defstates` _incrementally_, the same way you would add functions to an application.
-
-### Code navigation
-
-Component changes the way the code is structured. Depending on the size of the code base, and how rich the dependency graph is, Component might add a good amount of cognitive load. To a simple navigation from namespace to namespace, from function to function, Components add, well.. "Components" that can't be ignored when [loading the codebase in one's head](http://paulgraham.com/head.html)
-
-Since Mount relies on Clojure namespaces (`:require`/`:use`), navigation across functions / states is exactly
-the same with or without Mount: there are no extra mental steps.
-
-### Starting and stopping _parts_ of an application
-
-Component can't really start and stop parts of an application within the same "system". Other sub systems can be
-created from scratch or by dissoc'ing / merging with existing systems, but it is usually not all
-that flexible in terms of REPL sessions where lots of time is spent.
-
-Mount _can_ start and stop parts of an application via given states with their namespaces:
-
-```clojure
-dev=> (mount/start #'app.config/app-config #'app.nyse/conn)
-
-11:35:06.753 [nREPL-worker-1] INFO mount - >> starting.. app-config
-11:35:06.756 [nREPL-worker-1] INFO mount - >> starting.. conn
-:started
-dev=>
-```
-
-Here is more [documentation](../README.md#start-and-stop-parts-of-application) on how to start/stop parts of an app.
-
-### Boilerplate code
-
-Component does not require a whole lot of "extra" code but:
-
-* a system with dependencies
-* components as records
-* with optional constructors
-* and a Lifecycle/start Lifecycle/stop implementations
-* destructuring component maps
-
-Depending on the number of application components the "extra" size may vary.
-
-Mount is pretty much:
-
-```clojure
-(defstate name :start (fn)
- :stop (fn))
-```
-
-no "ceremony".
-
-## What Component does better
-
-### Swapping alternate implementations
-
-This is someting that is very useful for testing and is very easy to do in Component by simply assoc'ing onto a map.
-
-Mount can do it to: https://github.com/tolitius/mount#swapping-alternate-implementations
-
-The reason it is in "Component does it better" section is because, while result is the same, merging maps is a bit simpler than:
-
-```clojure
-(mount/start-with {#'app.nyse/db #'app.test/test-db
- #'app.nyse/publisher #'app.test/test-publisher})
-```
-
-### Uberjar / Packaging
-
-Since Component fully controls the `system` where the whole application lives, it is quite simple
-to start an application from anywhere including a `-main` function of the uberjar.
-
-In order to start the whole system in development, Mount just needs `(mount/start)` or `(reset)`
-it's [simple](https://github.com/tolitius/mount#the-importance-of-being-reloadable).
-
-However there is no "tools.namespaces"/REPL at a "stand alone jar runtime" and in order for Mount to start / stop
-the app, states need to be `:require`/`:use`d, which is usually done within the same namespace as `-main`.
-
-Depending on app dependencies, it could only require a few states to be `:require`/`:use`d, others
-will be brought transitively. Here is an [example](uberjar.md#creating-reloadable-uberjarable-app) of building a wepapp uberjar with Mount.
-
-On the flip side, Component _system_ usually requires lots of `:require`s as well, since in order to be built, it needs to "see" all the top level states.
-
-###### _conclusion: it's simple in Mount as well, but requires an additional step._
-
-### Multiple separate systems
-
-With Component multiple separate systems can be started _in the same Clojure runtime_ with different settings. Which is very useful for testing.
-
-Mount keeps states in namespaces, hence the app becomes "[The One](https://en.wikipedia.org/wiki/Neo_(The_Matrix))", and there can't be "multiples The Ones".
-
-Testing is not alien to Mount and it knows how to do a thing or two:
-
-* [starting / stopping parts of an application](https://github.com/tolitius/mount/blob/master/doc/differences-from-component.md#starting-and-stopping-parts-of-an-application)
-* [start an application without certain states](https://github.com/tolitius/mount#start-an-application-without-certain-states)
-* and [swapping alternate implementations](https://github.com/tolitius/mount#swapping-alternate-implementations)
-
-But running two apps in the same JVM side by side with "same but different" states, is not something Mount can do at the moment.
-
-###### _conclusion: needs more thinking._
-
-### Visualizing dependency graph
-
-Component keeps an actual graph which can be visualized with great libraries like [loom](https://github.com/aysylu/loom).
-Having this visualization is really helpful, especially during code discusions between multiple developers.
-
-Mount does not have this at the moment. It does have all the data to create such a visualization, perhaps even
-by building a graph out of the data it has just for this purpose.
diff --git a/doc/img/get-uberjar.png b/doc/img/get-uberjar.png
deleted file mode 100644
index 97b1dd9..0000000
Binary files a/doc/img/get-uberjar.png and /dev/null differ
diff --git a/doc/img/post-uberjar.png b/doc/img/post-uberjar.png
deleted file mode 100644
index 24f215b..0000000
Binary files a/doc/img/post-uberjar.png and /dev/null differ
diff --git a/doc/img/welcome-uberjar.png b/doc/img/welcome-uberjar.png
deleted file mode 100644
index ef112aa..0000000
Binary files a/doc/img/welcome-uberjar.png and /dev/null differ
diff --git a/doc/intro.md b/doc/intro.md
deleted file mode 100644
index f1e987b..0000000
--- a/doc/intro.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Introduction to statuo
-
-TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
diff --git a/doc/runtime-arguments.md b/doc/runtime-arguments.md
deleted file mode 100644
index d226cbb..0000000
--- a/doc/runtime-arguments.md
+++ /dev/null
@@ -1,130 +0,0 @@
-## Passing Runtime Arguments
-
-This example lives in the `with-args` branch. If you'd like to follow along:
-
-```bash
-$ git checkout with-args
-Switched to branch 'with-args'
-```
-
-## Start with args
-
-In order to pass runtime arguments, these could be `-this x -that y` params or `-Dparam=` or
-just a path to an external configuration file, `mount` has a special `start-with-args` function:
-
-```clojure
-(defn -main [& args]
- (mount/start-with-args args))
-```
-
-Most of the time it is better to parse args before they "get in", so usually accepting args would look something like:
-
-```clojure
-(defn -main [& args]
- (mount/start-with-args
- (parse-args args)))
-```
-
-where the `parse-args` is an app specific function.
-
-### Reading arguments
-
-Once the arguments are passed to the app, they are available via:
-
-```clojure
-(mount/args)
-```
-
-Which, unless the arguments were parsed or modified in the `-main` function,
-will return the original `args` that were passed to `-main`.
-
-### "Reading" example
-
-Here is an [example app](https://github.com/tolitius/mount/blob/with-args/test/app/app.clj) that takes `-main` arguments
-and parses them with [tools.cli](https://github.com/clojure/tools.cli):
-
-```clojure
-;; "any" regular function to pass arguments
-(defn parse-args [args]
- (let [opts [["-d" "--datomic-uri [datomic url]" "Datomic URL"
- :default "datomic:mem://mount"]
- ["-h" "--help"]]]
- (-> (parse-opts args opts)
- :options)))
-
-;; example of an app entry point with arguments
-(defn -main [& args]
- (mount/start-with-args
- (parse-args args)))
-```
-
-For the example sake the app reads arguments in two places:
-
-* [inside](https://github.com/tolitius/mount/blob/with-args/test/app/nyse.clj#L17) a `defstate`
-
-```clojure
-(defstate conn :start (new-connection (mount/args))
- :stop (disconnect (mount/args) conn))
-```
-
-* and from "any" [other place](https://github.com/tolitius/mount/blob/with-args/test/app/config.clj#L8) within a function:
-
-```clojure
-(defn load-config [path]
- ;; ...
- (if (:help (mount/args))
- (info "\n\nthis is a sample mount app to demo how to pass and read runtime arguments\n"))
- ;; ...)
-```
-
-### "Uber" example
-
-In order to demo all of the above, we'll build an uberjar:
-
-```bash
-$ lein do clean, uberjar
-...
-Created .. mount/target/mount-0.2.0-SNAPSHOT-standalone.jar
-```
-
-Since we have a default for a Datomic URI, it'll work with no arguments:
-
-```bash
-$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar
-
-22:12:03.290 [main] INFO mount - >> starting.. app-config
-22:12:03.293 [main] INFO mount - >> starting.. conn
-22:12:03.293 [main] INFO app.nyse - creating a connection to datomic: datomic:mem://mount
-22:12:03.444 [main] INFO mount - >> starting.. nrepl
-```
-
-Now let's ask it to help us:
-
-```bash
-$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar --help
-
-22:13:48.798 [main] INFO mount - >> starting.. app-config
-22:13:48.799 [main] INFO app.config -
-
-this is a sample mount app to demo how to pass and read runtime arguments
-
-22:13:48.801 [main] INFO mount - >> starting.. conn
-22:13:48.801 [main] INFO app.nyse - creating a connection to datomic: datomic:mem://mount
-22:13:48.946 [main] INFO mount - >> starting.. nrepl
-```
-
-And finally let's connect to the Single Malt Database. It's Friday..
-
-```bash
-$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar -d datomic:mem://single-malt-database
-
-22:16:10.733 [main] INFO mount - >> starting.. app-config
-22:16:10.737 [main] INFO mount - >> starting.. conn
-22:16:10.737 [main] INFO app.nyse - creating a connection to datomic: datomic:mem://single-malt-database
-22:16:10.885 [main] INFO mount - >> starting.. nrepl
-```
-
-### Other usecases
-
-Depending the requirements, these runtime arguments could take different shapes of forms. You would have a full control
-over what is passed to the app, the same way you have it without mount through `-main [& args]`.
diff --git a/doc/uberjar.md b/doc/uberjar.md
deleted file mode 100644
index 1300840..0000000
--- a/doc/uberjar.md
+++ /dev/null
@@ -1,163 +0,0 @@
-## Creating Reloadable Uberjar'able App
-
-This example lives in the `uberjar` branch. If you'd like to follow along:
-
-```bash
-$ git checkout uberjar
-Switched to branch 'uberjar'
-```
-
-### App state
-
-Here is an example [app](https://github.com/tolitius/mount/tree/uberjar/test/app) that has these states:
-
-```clojure
-16:20:44.997 [nREPL-worker-0] INFO mount - >> starting.. app-config
-16:20:44.998 [nREPL-worker-0] INFO mount - >> starting.. conn
-16:20:45.393 [nREPL-worker-0] INFO mount - >> starting.. nyse-app
-16:20:45.443 [nREPL-worker-0] INFO mount - >> starting.. nrepl
-```
-
-where `nyse-app` is _the_ app. It has the usual routes:
-
-```clojure
-(defroutes mount-example-routes
-
- (GET "/" [] "welcome to mount sample app!")
- (GET "/nyse/orders/:ticker" [ticker]
- (generate-string (find-orders ticker)))
-
- (POST "/nyse/orders" [ticker qty bid offer]
- (add-order ticker (bigdec bid) (bigdec offer) (Integer/parseInt qty))
- (generate-string {:added {:ticker ticker
- :qty qty
- :bid bid
- :offer offer}})))
-```
-
-and the reloadable state:
-
-```clojure
-(defn start-nyse []
- (create-nyse-schema) ;; creating schema (usually done long before the app is started..)
- (-> (routes mount-example-routes)
- (handler/site)
- (run-jetty {:join? false
- :port (get-in app-config [:www :port])})))
-
-(defstate nyse-app :start (start-nyse)
- :stop (.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point
-```
-
-In order not to block, and being reloadable, the Jetty server is started in the "`:join? false`" mode which starts the server,
-and just returns a reference to it, so it can be easily stopped by `(.stop server)`
-
-### "Uberjar is the :main"
-
-In order for a standalone jar to run, it needs an entry point. This sample app [has one](https://github.com/tolitius/mount/blob/uberjar/test/app/app.clj#L16):
-
-```clojure
-;; example of an app entry point
-(defn -main [& args]
- (mount/start))
-```
-
-And some usual suspects from `project.clj`:
-
-```clojure
-;; "test" is in sources here to just "demo" the uberjar without poluting mount "src"
-:uberjar {:source-paths ["test/app"]
- :dependencies [[compojure "1.4.0"]
- [ring/ring-jetty-adapter "1.1.0"]
- [cheshire "5.5.0"]
- [org.clojure/tools.nrepl "0.2.11"]
- [com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]
- :main app
- :aot :all}}
-```
-
-### REPL time
-
-```clojure
-$ lein do clean, repl
-
-user=> (dev)(reset)
-16:20:44.997 [nREPL-worker-0] INFO mount - >> starting.. app-config
-16:20:44.998 [nREPL-worker-0] INFO mount - >> starting.. conn
-16:20:45.393 [nREPL-worker-0] INFO mount - >> starting.. nyse-app
-
-16:20:45.442 [nREPL-worker-0] INFO o.e.jetty.server.AbstractConnector - Started SelectChannelConnector@0.0.0.0:53600
-
-16:20:45.443 [nREPL-worker-0] INFO mount - >> starting.. nrepl
-:ready
-dev=>
-```
-
-Jetty server is started and ready to roll. And everything is still reloadable:
-
-```clojure
-dev=> (reset)
-16:44:16.625 [nREPL-worker-2] INFO mount - << stopping.. nrepl
-16:44:16.626 [nREPL-worker-2] INFO mount - << stopping.. nyse-app
-16:44:16.711 [nREPL-worker-2] INFO mount - << stopping.. conn
-16:44:16.713 [nREPL-worker-2] INFO mount - << stopping.. app-config
-
-16:44:16.747 [nREPL-worker-2] INFO mount - >> starting.. app-config
-16:44:16.748 [nREPL-worker-2] INFO mount - >> starting.. conn
-16:44:16.773 [nREPL-worker-2] INFO mount - >> starting.. nyse-app
-
-16:44:16.777 [nREPL-worker-2] INFO o.e.jetty.server.AbstractConnector - Started SelectChannelConnector@0.0.0.0:54476
-
-16:44:16.778 [nREPL-worker-2] INFO mount - >> starting.. nrepl
-```
-
-Notice the Jetty port difference between reloads: `53600` vs. `54476`. This is done on purpose via [config](https://github.com/tolitius/mount/blob/uberjar/test/resources/config.edn#L4):
-
-```clojure
-:www {:port 0} ;; Jetty will randomly assign the available port (this is good for dev reloadability)
-```
-
-This of course can be solidified for different env deployments. For example I like `4242` :)
-
-### Packaging one super uber jar
-
-```clojure
-$ lein do clean, uberjar
-...
-Created /Users/tolitius/1/fun/mount/target/mount-0.1.0-SNAPSHOT-standalone.jar ;; your version may vary
-```
-
-Let's give it a spin:
-
-```bash
-$ java -jar target/mount-0.1.0-SNAPSHOT-standalone.jar
-...
-16:51:35.586 [main] DEBUG o.e.j.u.component.AbstractLifeCycle - STARTED SelectChannelConnector@0.0.0.0:54728
-```
-
-Up and running on port `:54728`:
-
-
-
-See if we have any orders:
-
-
-
-we don't. let's put something into Datomic:
-
-```clojure
-$ curl -X POST -d "ticker=GOOG&qty=100&bid=665.51&offer=665.59" "http://localhost:54728/nyse/orders"
-{"added":{"ticker":"GOOG","qty":"100","bid":"665.51","offer":"665.59"}}
-```
-
-now we should:
-
-
-
-### Choices
-
-There are multiple ways to start a web app. This above is the most straighforward one: start server / stop server.
-
-But depending on the requirements / architecture, the app can also have an entry point to `(mount/start)`
-via something like [:ring :init](https://github.com/weavejester/lein-ring#general-options)). Or the `(mount/start)`
-can go into the handler function, etc.
diff --git a/project.clj b/project.clj
index 857fbd7..c47068f 100644
--- a/project.clj
+++ b/project.clj
@@ -1,4 +1,4 @@
-(defproject mount "0.1.4-SNAPSHOT"
+(defproject mount "0.1.5-SNAPSHOT"
:description "managing Clojure app state since (reset)"
:url "https://github.com/tolitius/mount"
:license {:name "Eclipse Public License"
@@ -6,26 +6,22 @@
:source-paths ["src"]
+ ;; these dependencies are here for uberjar and dev example
:dependencies [[org.clojure/clojure "1.7.0"]
- [ch.qos.logback/logback-classic "1.1.3"]
[org.clojure/tools.logging "0.3.1"]
- [org.clojure/tools.macro "0.1.2"]
- [org.clojure/tools.namespace "0.2.11"]]
+ [ch.qos.logback/logback-classic "1.1.3"]
+ [compojure "1.4.0"]
+ [ring/ring-jetty-adapter "1.1.0"]
+ [cheshire "5.5.0"]
+ [com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]
+ [robert/hooke "1.3.0"]
+ [org.clojure/tools.nrepl "0.2.11"]
+ [org.clojure/tools.macro "0.1.2"]]
:profiles {:dev {:source-paths ["dev" "test/app"]
- :dependencies [[yesql "0.5.1"]
- [compojure "1.4.0"]
- [ring/ring-jetty-adapter "1.1.0"]
- [cheshire "5.5.0"]
- [org.clojure/tools.nrepl "0.2.11"]
- [com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]}
+ :dependencies [[org.clojure/tools.namespace "0.2.11"]]}
;; "test" is in sources here to just "demo" the uberjar without poluting mount "src"
:uberjar {:source-paths ["test/app"]
- :dependencies [[compojure "1.4.0"]
- [ring/ring-jetty-adapter "1.1.0"]
- [cheshire "5.5.0"]
- [org.clojure/tools.nrepl "0.2.11"]
- [com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]
:main app
:aot :all}})
diff --git a/src/mount/core.clj b/src/mount/core.clj
index c03be64..6ca57c4 100644
--- a/src/mount/core.clj
+++ b/src/mount/core.clj
@@ -1,15 +1,14 @@
(ns mount.core
- (:require [clojure.tools.macro :as macro]
- [clojure.tools.namespace.repl :refer [disable-reload!]]
- [clojure.tools.logging :refer [info warn debug error]]))
+ (:require [clojure.tools.macro :as macro]))
-(disable-reload!)
-
-;; (defonce ^:private session-id (System/currentTimeMillis))
(defonce ^:private mount-state 42)
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
(defonce ^:private state-seq (atom 0))
(defonce ^:private state-order (atom {}))
+(defonce ^:private running (atom {})) ;; to clean dirty states on redefs
+
+;; supporting tools.namespace: (disable-reload!)
+(alter-meta! *ns* assoc ::load false) ;; to exclude the dependency
(defn- make-state-seq [state]
(or (@state-order state)
@@ -30,63 +29,87 @@
(and suspend (not resume)) (throw
(IllegalArgumentException. "suspendable state should have a resume function (i.e. missing :resume fn)"))))
+(defn- with-ns [ns name]
+ (str ns "/" name))
+
+(defn- pounded? [f]
+ (let [pound "(fn* [] "] ;;TODO: think of a better (i.e. typed) way to distinguish #(f params) from (fn [params] (...)))
+ (.startsWith (str f) pound)))
+
+(defn- unpound [f]
+ (if (pounded? f)
+ (nth f 2) ;; magic 2 is to get the body => ["fn*" "[]" "(fn body)"]
+ f))
+
+(defn- cleanup-if-dirty
+ "in case a namespace is recompiled without calling (mount/stop),
+ a running state instance will still be running.
+ this function stops this 'lost' state instance.
+ it is meant to be called by defstate before defining a new state"
+ [state]
+ (when-let [stop (@running state)]
+ (stop)))
+
(defmacro defstate [state & body]
(let [[state params] (macro/name-with-attributes state body)
{:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)]
(validate lifecycle)
+ (cleanup-if-dirty (with-ns *ns* state))
(let [s-meta (cond-> {:mount-state mount-state
- :order (make-state-seq state)
- :start `(fn [] (~@start))
- :started? false}
- stop (assoc :stop `(fn [] (~@stop)))
- suspend (assoc :suspend `(fn [] (~@suspend)))
- resume (assoc :resume `(fn [] (~@resume))))]
+ :order (make-state-seq (with-ns *ns* state))
+ :start `(fn [] ~start)
+ :status #{:stopped}}
+ stop (assoc :stop `(fn [] ~(unpound stop)))
+ suspend (assoc :suspend `(fn [] ~suspend))
+ resume (assoc :resume `(fn [] ~resume)))]
`(defonce ~(with-meta state (merge (meta state) s-meta))
(NotStartedState. ~(str state))))))
-(defn- up [var {:keys [ns name start started? resume suspended?]}]
- (when-not started?
- (let [s (try (if suspended?
- (do (info ">> resuming.. " name)
- (resume))
- (do (info ">> starting.. " name)
- (start)))
+(defn- record! [{:keys [ns name]} f done]
+ (let [state (f)]
+ (swap! done conj (ns-resolve ns name))
+ state))
+
+(defn- up [var {:keys [ns name start stop resume status] :as state} done]
+ (when-not (:started status)
+ (let [s (try (if (:suspended status)
+ (record! state resume done)
+ (record! state start done))
(catch Throwable t
(throw (RuntimeException. (str "could not start [" name "] due to") t))))]
(intern ns (symbol name) s)
- (alter-meta! var assoc :started? true :suspended? false))))
+ (swap! running assoc (with-ns ns name) stop)
+ (alter-meta! var assoc :status #{:started}))))
-(defn- down [var {:keys [ns name stop started? suspended?]}]
- (when (or started? suspended?)
- (info "<< stopping.. " name)
+(defn- down [var {:keys [ns name stop status] :as state} done]
+ (when (some status #{:started :suspended})
(when stop
(try
- (stop)
+ (record! state stop done)
(catch Throwable t
(throw (RuntimeException. (str "could not stop [" name "] due to") t)))))
(intern ns (symbol name) (NotStartedState. name)) ;; (!) if a state does not have :stop when _should_ this might leak
- (alter-meta! var assoc :started? false :suspended? false)))
+ (swap! running dissoc (with-ns ns name))
+ (alter-meta! var assoc :status #{:stopped})))
-(defn- sigstop [var {:keys [ns name started? suspend resume]}]
- (when (and started? resume) ;; can't have suspend without resume, but the reverse is possible
- (info ">> suspending.. " name)
- (when suspend ;; don't suspend if there is only resume function (just mark it :suspended?)
- (let [s (try (suspend)
+(defn- sigstop [var {:keys [ns name suspend resume status] :as state} done]
+ (when (and (:started status) resume) ;; can't have suspend without resume, but the reverse is possible
+ (when suspend ;; don't suspend if there is only resume function (just mark it :suspended?)
+ (let [s (try (record! state suspend done)
(catch Throwable t
(throw (RuntimeException. (str "could not suspend [" name "] due to") t))))]
(intern ns (symbol name) s)))
- (alter-meta! var assoc :started? false :suspended? true)))
+ (alter-meta! var assoc :status #{:suspended})))
-(defn- sigcont [var {:keys [ns name start started? resume suspended?]}]
+(defn- sigcont [var {:keys [ns name start resume status] :as state} done]
(when (instance? NotStartedState var)
(throw (RuntimeException. (str "could not resume [" name "] since it is stoppped (i.e. not suspended)"))))
- (when suspended?
- (info ">> resuming.. " name)
- (let [s (try (resume)
+ (when (:suspended status)
+ (let [s (try (record! state resume done)
(catch Throwable t
(throw (RuntimeException. (str "could not resume [" name "] due to") t))))]
(intern ns (symbol name) s)
- (alter-meta! var assoc :started? true :suspended? false))))
+ (alter-meta! var assoc :status #{:started}))))
;;TODO args might need more thinking
(defn args [] @-args)
@@ -111,35 +134,37 @@
(defn states-with-deps []
(let [all (find-all-states)]
(->> (map (comp #(add-deps % all)
- #(select-keys % [:name :order :ns :started? :suspended?])
+ #(select-keys % [:name :order :ns :status])
meta)
all)
(sort-by :order))))
(defn- bring [states fun order]
- (->> states
- (sort-by (comp :order meta) order)
- (map #(fun % (meta %)))
- doall))
+ (let [done (atom [])]
+ (->> states
+ (sort-by (comp :order meta) order)
+ (map #(fun % (meta %) done))
+ dorun)
+ @done))
-(defn merge-lifecycles
+(defn- merge-lifecycles
"merges with overriding _certain_ non existing keys.
i.e. :suspend is in a 'state', but not in a 'substitute': it should be overriden with nil
however other keys of 'state' (such as :ns,:name,:order) should not be overriden"
([state sub]
(merge-lifecycles state nil sub))
- ([state origin {:keys [start stop suspend resume suspended?]}]
+ ([state origin {:keys [start stop suspend resume status]}]
(assoc state :origin origin
- :suspended? suspended?
+ :status status
:start start :stop stop :suspend suspend :resume resume)))
-(defn rollback! [state]
+(defn- rollback! [state]
(let [{:keys [origin]} (meta state)]
(when origin
(alter-meta! state #(merge-lifecycles % origin)))))
-(defn substitute! [state with]
- (let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :suspended?])
+(defn- substitute! [state with]
+ (let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :status])
origin (meta state)
sub (meta with)]
(alter-meta! with assoc :sub? true)
@@ -147,31 +172,26 @@
(defn- unsub [state]
(when (-> (meta state) :sub?)
- (alter-meta! state assoc :sub? nil
- :started false)))
+ (alter-meta! state dissoc :sub?)))
(defn- all-without-subs []
(remove (comp :sub? meta) (find-all-states)))
(defn start [& states]
(let [states (or (seq states) (all-without-subs))]
- (bring states up <)
- :started))
+ {:started (bring states up <)}))
(defn stop [& states]
- (let [states (or states (find-all-states))]
- (doall (map unsub states)) ;; unmark substitutions marked by "start-with"
- (bring states down >)
- (doall (map rollback! states)) ;; restore to origin from "start-with"
- :stopped))
+ (let [states (or states (find-all-states))
+ _ (dorun (map unsub states)) ;; unmark substitutions marked by "start-with"
+ stopped (bring states down >)]
+ (dorun (map rollback! states)) ;; restore to origin from "start-with"
+ {:stopped stopped}))
(defn stop-except [& states]
(let [all (set (find-all-states))
states (remove (set states) all)]
- (doall (map unsub states)) ;; unmark substitutions marked by "start-with"
- (bring states down >)
- (doall (map rollback! states)) ;; restore to origin from "start-with"
- :stopped))
+ (apply stop states)))
(defn start-with-args [xs & states]
(reset! -args xs)
@@ -180,9 +200,8 @@
(start)))
(defn start-with [with]
- (doall
- (for [[from to] with]
- (substitute! from to)))
+ (doseq [[from to] with]
+ (substitute! from to))
(start))
(defn start-without [& states]
@@ -194,10 +213,8 @@
(defn suspend [& states]
(let [states (or (seq states) (all-without-subs))]
- (bring states sigstop <)
- :suspended))
+ {:suspended (bring states sigstop <)}))
(defn resume [& states]
(let [states (or (seq states) (all-without-subs))]
- (bring states sigcont <)
- :resumed))
+ {:resumed (bring states sigcont <)}))
diff --git a/test/app/utils/logging.clj b/test/app/utils/logging.clj
new file mode 100644
index 0000000..dc0216c
--- /dev/null
+++ b/test/app/utils/logging.clj
@@ -0,0 +1,46 @@
+(ns app.utils.logging ;; << change to your namespace/path
+ (:require [mount.core]
+ [robert.hooke :refer [add-hook clear-hooks]]
+ [clojure.string :refer [split]]
+ [clojure.tools.logging :refer [info]]))
+
+(alter-meta! *ns* assoc ::load false)
+
+(defn- f-to-action [f]
+ (let [fname (-> (str f)
+ (split #"@")
+ first)]
+ (case fname
+ "mount.core$up" :up
+ "mount.core$down" :down
+ "mount.core$sigstop" :suspend
+ "mount.core$sigcont" :resume
+ :noop)))
+
+(defn whatcha-doing? [{:keys [status suspend]} action]
+ (case action
+ :up (if (status :suspended) ">> resuming"
+ (if-not (status :started) ">> starting"))
+ :down (if (or (status :started) (status :suspended)) "<< stopping")
+ :suspend (if (and (status :started) suspend) "<< suspending")
+ :resume (if (status :suspended) ">> resuming")))
+
+(defn log-status [f & args]
+ (let [{:keys [ns name] :as state} (second args)
+ action (f-to-action f)]
+ (when-let [taking-over-the-world (whatcha-doing? state action)]
+ (info (str taking-over-the-world ".. " (ns-resolve ns name))))
+ (apply f args)))
+
+(defonce lifecycle-fns
+ #{#'mount.core/up
+ #'mount.core/down
+ #'mount.core/sigstop
+ #'mount.core/sigcont})
+
+(defn without-logging-status []
+ (doall (map #(clear-hooks %) lifecycle-fns)))
+
+(defn with-logging-status []
+ (without-logging-status)
+ (doall (map #(add-hook % log-status) lifecycle-fns)))