Compare commits

..

No commits in common. "master" and "0.1.12" have entirely different histories.

50 changed files with 210 additions and 607 deletions

View file

@ -1 +0,0 @@
{:config-paths ["../resources/clj-kondo.exports/mount/mount/"]}

6
.gitignore vendored
View file

@ -1,10 +1,7 @@
/target
/classes
/checkouts
.cpcache/
.rebel_readline_history
cljs-test-runner-out
node_modules
pom.xml
pom.xml.asc
.repl*
dev/resources/public/js/*
@ -21,4 +18,3 @@ doo-index.html
/.nrepl-history
.cljs_rhino_repl/
out/
.clj-kondo/.cache

View file

@ -1,26 +0,0 @@
sudo: false
language: java
script:
- boot test
- boot test-cljs
- boot test-cljs-advanced
install:
- mkdir -p ~/bin
- export PATH=~/bin:$PATH
- curl -L https://github.com/boot-clj/boot-bin/releases/download/latest/boot.sh -o ~/bin/boot
- chmod +x ~/bin/boot
jdk: openjdk8
env:
matrix:
- BOOT_CLOJURE_VERSION=1.8.0
global:
- JAVA_OPTS="-Xms512m -Xmx2048m"
jdk:
- openjdk8
cache:
directories:
- $HOME/.m2
- $HOME/.boot/cache/bin
- $HOME/.boot/cache/lib
- $HOME/bin

View file

@ -1,38 +1,3 @@
## 0.1.19
###### Wed Aug 21 22:43:40 2024 -0400
* add `clojure-kondo`
## 0.1.17
###### Mon Dec 19 15:17:21 2022 -0500
* 2d050e9 fix: swap-states non string rollback (thanks to [egg-juxt](https://github.com/egg-juxt))
## 0.1.16
###### Mon Jan 28 10:34:26 2019 -0500
* fb52f79 prevent reloading of mount.core ns ([106](https://github.com/tolitius/mount/issues/106))
* c5f3e4c current-state should return Derefable on :cljc ([104](https://github.com/tolitius/mount/issues/104))
* c2687d1 silent `*logger*` warning in latest ClojureScript([101](https://github.com/tolitius/mount/issues/101))
* bb23747 switch form circle to travis
## 0.1.14
###### Thu Oct 25 18:34:22 2018 -0400
* cljs: throw js/Error not just a string ([#100](https://github.com/tolitius/mount/issues/100))
* add ^{:on-lazy-start :throw} ([#95](https://github.com/tolitius/mount/issues/95) and [#99](https://github.com/tolitius/mount/issues/99))
* self hosted ClojureScript support ([#85](https://github.com/tolitius/mount/issues/85) and [#97](https://github.com/tolitius/mount/issues/97))
## 0.1.12
###### Sat Feb 10 14:53:04 2018 -0500
* remove ref to old state alias in `swap-states` ([#89](https://github.com/tolitius/mount/issues/89))
* `:on-reload :noop` should not leave stale references
* `stop-except` & `start-without` to take all states ([#81](https://github.com/tolitius/mount/issues/81))
* goog.log/error for logging cljs errors ([#63](https://github.com/tolitius/mount/issues/63))
* on failure (down) reports and cleans up vs. just "throw" ([#63](https://github.com/tolitius/mount/issues/63))
* do not start a `DerefableState` as a side-effect of printing ([#76](https://github.com/tolitius/mount/issues/76))
## 0.1.11
###### Fri Dec 2 18:09:51 2016 -0600

View file

@ -1,31 +0,0 @@
.PHONY: clean test jar tag outdated install deploy tree repl
clean:
rm -rf target
rm -rf classes
jar: clean test tag
clojure -A:jar
test: clean
clojure -X:test :patterns '[".*"]' # clojure tests
# clojure -Atest-cljs -r ".*test.self.host.*" # clojure script tests
# run "j8; boot test-cljs" until running cljs tests via deps.edn is fixed
outdated:
clojure -M:outdated
tag:
clojure -A:tag
install: jar
clojure -A:install
deploy: jar
clojure -A:deploy
tree:
mvn dependency:tree
repl:
clojure -A:dev -A:repl

View file

@ -1,13 +1,19 @@
> I think that it's _extraordinarily important_ that we in computer science keep fun in computing
_**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](https://web.mit.edu/6.001/6.037/sicp.pdf)_
_**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](https://mitpress.mit.edu/sicp/full-text/book/book-Z-H-3.html)_
# mount <img src="doc/img/mount-logo.png" width="70px">
[![<! release](https://img.shields.io/badge/dynamic/json.svg?label=release&url=https%3A%2F%2Fclojars.org%2Fmount%2Flatest-version.json&query=version&colorB=blue)](https://github.com/tolitius/mount/releases)
[![<! clojars](https://img.shields.io/clojars/v/mount.svg)](https://clojars.org/mount)
###### _any_ questions or feedback: [`#mount`](https://clojurians.slack.com/messages/mount/) clojurians slack channel <img src="doc/img/slack-icon.png" width="15px"> (or just [open an issue](https://github.com/tolitius/mount/issues))
module | branch | status
----------|----------|----------
mount | `master` | [![Circle CI](https://circleci.com/gh/tolitius/mount/tree/master.png?style=svg)](https://circleci.com/gh/tolitius/mount/tree/master)
[![Clojars Project](http://clojars.org/mount/latest-version.svg)](http://clojars.org/mount)
> <img src="doc/img/slack-icon.png" width="30px"> _any_ questions or feedback: [`#mount`](https://clojurians.slack.com/messages/mount/) clojurians slack channel (or just [open an issue](https://github.com/tolitius/mount/issues))
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Why?](#why)
@ -28,8 +34,6 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
- [Swapping States with States](#swapping-states-with-states)
- [Stop an Application Except Certain States](#stop-an-application-except-certain-states)
- [ClojureScript is Clojure](doc/clojurescript.md#managing-state-in-clojurescript)
- [cljc mode](#cljc-mode)
- [Disable Lazy Start](#disable-lazy-start)
- [Packaging](#packaging)
- [Affected States](#affected-states)
- [Recompiling Namespaces with Running States](#recompiling-namespaces-with-running-states)
@ -90,7 +94,7 @@ Creating state is easy:
(defstate conn :start (create-conn))
```
where the `create-conn` function creates a connection (for example to a database) and 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 / destroyed between reloads, there is also `:stop`
@ -103,7 +107,7 @@ That is pretty much it. But wait, there is more.. this state is _a top level bei
`required` by other namespaces or in REPL:
```clojure
dev=> (require '[app.db :refer [conn]])
dev=> (require '[app.nyse :refer [conn]])
nil
dev=> conn
#object[datomic.peer.LocalConnection 0x1661a4eb "datomic.peer.LocalConnection@1661a4eb"]
@ -120,22 +124,6 @@ For example let's say an `app` needs a connection above. No problem:
where `above` is an arbitrary namespace that defines the above state / connection.
### Documentation String
As in any definition (i.e. `def`, `defn`) a documentation string can be added to better describe a state:
```clojure
(defstate answer
"answer to the ultimate question of life universe and everything"
:start (+ 1 41))
```
```clojure
(doc answer)
-------------------------
dev/answer
answer to the ultimate question of life universe and everything
```
## Dependencies
If the whole app is one big application context (or `system`), cross dependencies with a solid dependency graph
@ -172,9 +160,8 @@ this `config`, being top level, can be used in other namespaces, including the o
(defstate conn :start (create-connection config))
```
[here](dev/clj/app/www.clj#L32) is an example of a web server that "depends" on a similar `config`.
###### _(the example `load-config` function above comes from [cprop](https://github.com/tolitius/cprop), but could of course be a custom function that loads configuration from a file)_
[here](dev/clj/app/www.clj#L32)
is an example of a web server that "depends" on a similar `config`.
## Value of values
@ -405,7 +392,7 @@ The `start-with-states` function takes values in a form of `{:start fn :stop fn}
```
`start-with-states` takes a map of states with their substitutes. For example `#'app.nyse/db` here is the real deal (remote) DB that is being
substituted with `#(connect test-config)` function, which could end up being anything, a map, an in memory DB, etc.
substituted with `#(connect test-config)` function, which could endup being anything, a map, an in memory DB, etc.
The `:stop` functions of substitutes can be anything, and could refer to the original state references. As in the example above: `db` and `publisher`
are real references. They would need to be accessible from the namespace of course, so you might need to `(:require [app.neo :refer [db]])`
@ -548,56 +535,6 @@ Mount detected that `#'dev/won't-be-here-long` was deleted, hence:
<< stopping.. #'dev/won't-be-here-long (it was deleted)
```
## `cljc` mode
By default mount states are kept under var references. While it works for Clojure, it falls short in the land of ClojureScript since, especially during an `:advanced` compilation, var names get compressed + ClojureScript does not support reified vars.
To support both Clojure and ClojureScript mount has a `cljc` mode which is well documented in [here](doc/clojurescript.md#managing-state-in-clojurescript), and can be enabled by `(mount/in-cljc-mode)`.
### Disable Lazy Start
When in `cljc` mode, mount states that are not started by `(mount/start a b c)`, or that are not transitive states: i.e. not `:require`d at the time `(mount/start)` is called, will start lazily whenever they are dereferenced:
```clojure
=> (mount/in-cljc-mode)
:cljc
=> (defstate db-connection :start (println "connecting")
:stop (println "disconnecting..."))
=> db-connection
#object[mount.core.DerefableState 0x546b9d51 {:status :pending, :val nil}]
dev=> (mount/running-states)
#{}
dev=> @db-connection ;; db-connection will start here when deref'ed even though it was not started explicitly
connecting
dev=> (mount/running-states)
#{"#'dev/db-connection"}
```
This can be quite handy as it allows certain app states to start lazily.
However there are cases when it is best to fail in case a certain state is deref'ed while it was not yet started. This is possible by marking such states with `^{:on-lazy-start :throw}` metadata:
```clojure
=> (defstate ^{:on-lazy-start :throw} db-connection :start (do (println "connecting") 42)
:stop (println "disconnecting..."))
=> @db-connection ;; this will throw since db connection is deref'ed before it was started
java.lang.RuntimeException: :on-lazy-start is set to :throw i.e. (defstate {:on-lazy-start :throw} #'dev/db-connection...) and #'dev/db-connection state was not explicitly started before it was deref'ed (i.e. @#'dev/db-connection)
=> (mount/start #'dev/db-connection)
connecting
{:started ["#'dev/db-connection"]}
=> @db-connection
42
```
## Packaging
Since `mount` relies on the Clojure/Script Compiler to learn about all the application states, before `mount/start` is called all the namespaces that have `defstate`s need to be compiled.
@ -861,7 +798,7 @@ The documentation is [here](doc/runtime-arguments.md#passing-runtime-arguments).
## License
Copyright © 2020 tolitius
Copyright © 2017 tolitius
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.

View file

@ -1,2 +0,0 @@
BOOT_VERSION=2.7.1
BOOT_CLOJURE_VERSION=1.10.1

View file

@ -1,18 +1,8 @@
(def +version+ "0.1.18")
;; -- since boot is no more, this file will go away
;; -- once deps.edn "learns" how to run all cljs tests
;; j8; boot test
;; j8; boot test-cljs
;; j8; boot test-cljs-advanced
;; j8; boot -v build-jar push-snapshot
;; j8; boot -v build-jar push-release
(def +version+ "0.1.12")
(set-env!
:source-paths #{"src"}
:resource-paths #{"resources"}
:dependencies '[;; mount brings _NO DEPENDENCIES_, everything here is for
:dependencies '[;; mount brings _no dependencies_, everything here is for
;; mount dev, examples apps and tests
[org.clojure/clojure "1.8.0" :scope "provided"]
@ -23,7 +13,6 @@
[cheshire "5.5.0" :scope "provided"]
[hiccups "0.3.0" :scope "provided" :exclusions [org.clojure/clojurescript]]
[com.andrewmcveigh/cljs-time "0.3.14" :scope "provided"]
[tolitius/yang "0.1.9" :scope "provided"]
[ch.qos.logback/logback-classic "1.1.3" :scope "provided"]
[org.clojure/tools.logging "0.3.1" :scope "provided"]
[robert/hooke "1.3.0" :scope "provided"]
@ -44,12 +33,11 @@
;; boot cljs
[adzerk/boot-cljs "1.7.228-1" :scope "test"]
[adzerk/boot-cljs-repl "0.4.0" :scope "test"]
[cider/piggieback "0.3.9" :scope "test" :exclusions [org.clojure/clojurescript]]
[weasel "0.7.0" :scope "test" :exclusions [org.clojure/clojurescript]]
[nrepl "0.4.5" :scope "test"]
[adzerk/boot-cljs-repl "0.3.0" :scope "test"]
[pandeiro/boot-http "0.7.1-SNAPSHOT" :scope "test"]
[tolitius/boot-stripper "0.1.0-SNAPSHOT" :scope "test"]
[com.cemerick/piggieback "0.2.1" :scope "test" :exclusions [org.clojure/clojurescript]]
[weasel "0.7.0" :scope "test" :exclusions [org.clojure/clojurescript]]
[adzerk/boot-reload "0.4.8" :scope "test"]
[crisptrutski/boot-cljs-test "0.2.1-SNAPSHOT" :scope "test"]])
@ -144,8 +132,7 @@
(task-options!
tcs/test-cljs {:js-env :phantom}
push {; :ensure-clean nil
:ensure-branch nil}
push {:ensure-branch nil}
pom {:project 'mount
:version +version+
:description "managing Clojure and ClojureScript app state since (reset)"

17
circle.yml Normal file
View file

@ -0,0 +1,17 @@
machine:
java:
version: oraclejdk8
environment:
_JAVA_OPTIONS: "-Xms512m -Xmx2048m"
dependencies:
pre:
- wget https://github.com/boot-clj/boot-bin/releases/download/latest/boot.sh
- mv boot.sh boot && chmod a+x boot && sudo mv boot /usr/local/bin
test:
override:
- boot test
- boot test-cljs
- boot test-cljs-advanced
- lein do clean, test2junit

View file

@ -1,52 +0,0 @@
{:paths ["src" "resources"]
:deps {} ;; deps no deps
:aliases {:dev {:extra-deps {metosin/jsonista {:mvn/version "0.3.8"}
com.datomic/datomic-free {:mvn/version "0.9.5359"
:exclusions [joda-time/joda-time]}
org.clojure/tools.nrepl {:mvn/version "0.2.12"}
org.clojure/tools.namespace {:mvn/version "0.2.11"}
cheshire/cheshire {:mvn/version "5.5.0"}
compojure/compojure {:mvn/version "1.5.0"}
ring/ring-jetty-adapter {:mvn/version "1.1.0"}
robert/hooke {:mvn/version "1.3.0"}
proto-repl/proto-repl {:mvn/version "0.3.1"}
proto-repl-charts/proto-repl-charts {:mvn/version "0.3.2"}
nrepl/nrepl {:mvn/version "0.7.0"}}}
:test {:extra-paths ["test/core" "test/clj" "test/cljs" "test/resources"]
:extra-deps {com.datomic/datomic-free {:mvn/version "0.9.5359"
:exclusions [joda-time/joda-time]}
org.clojure/tools.nrepl {:mvn/version "0.2.12"}
robert/hooke {:mvn/version "1.3.0"}
org.clojure/tools.logging {:mvn/version "1.3.0"}
io.github.cognitect-labs/test-runner {:git/url "https://github.com/cognitect-labs/test-runner.git"
:sha "e7660458ce25bc4acb4ccc3e2415aae0a4907198"}}
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}
:test-cljs {:extra-paths ["test/core" "test/cljs" "test/resources"]
:extra-deps {org.clojure/clojure {:mvn/version "1.8.0"}
org.clojure/clojurescript {:mvn/version "1.7.228"}
com.andrewmcveigh/cljs-time {:mvn/version "0.3.14"}
hiccups/hiccups {:mvn/version "0.3.0"}
datascript/datascript {:mvn/version "0.15.0"}
olical/cljs-test-runner {:mvn/version "3.8.1"}}
:main-opts ["-m" "cljs-test-runner.main"]}
:repl {:extra-paths ["dev/clj"]
:extra-deps {cider/cider-nrepl {:mvn/version "0.22.4"}
org.clojure/tools.logging {:mvn/version "1.2.4"}
com.bhauman/rebel-readline {:mvn/version "0.1.4"}}
:main-opts ["-e" "(require 'dev)(in-ns 'dev)"
"-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]"
"-i" "-f" "rebel-readline.main/-main"]}
:outdated {:extra-deps {olical/depot {:mvn/version "2.0.1"}}
:main-opts ["-m" "depot.outdated.main" "-a" "outdated"]}
:tag {:extra-deps {tolitius/tag {:mvn/version "0.1.7"}}
:main-opts ["-m" "tag.core" "tolitius/mount" "managing Clojure and ClojureScript app state since (reset)"]}
:jar {:extra-deps {seancorfield/depstar {:mvn/version "1.1.128"}}
:extra-paths ["target/about"]
:main-opts ["-m" "hf.depstar.jar" "target/mount.jar" "--exclude" "clojure/core/specs/alpha.*"]}
:deploy {:extra-deps {deps-deploy/deps-deploy {:mvn/version "RELEASE"}}
:main-opts ["-m" "deps-deploy.deps-deploy" "deploy" "target/mount.jar"]}
:install {:extra-deps {deps-deploy/deps-deploy {:mvn/version "RELEASE"}}
:main-opts ["-m" "deps-deploy.deps-deploy" "install" "target/mount.jar"]}}}

View file

@ -1,6 +1,7 @@
(ns dev
(:require [clojure.pprint :refer [pprint]]
[clojure.tools.namespace.repl :as tn]
[boot.core :refer [load-data-readers!]]
[mount.core :as mount :refer [defstate]]
[mount.tools.graph :refer [states-with-deps]]
[app.utils.logging :refer [with-logging-status]]
@ -40,11 +41,4 @@
(tn/refresh :after 'dev/go))
(mount/in-clj-mode)
(defn load-data-readers!
"Refresh *data-readers* with readers from newly acquired dependencies."
[]
(#'clojure.core/load-data-readers)
(set! *data-readers* (.getRawRoot #'*data-readers*)))
(load-data-readers!)

View file

@ -1,5 +1,5 @@
(ns proto-play
(:require [mount.tools.graph :as mg]
(:require [mount.tools.graph :as mount]
[proto-repl-charts.graph :as proto]))
(defn mount->proto [graph]
@ -10,6 +10,6 @@
{}
graph))
(->> (mg/states-with-deps)
(->> (mount/states-with-deps)
mount->proto
(proto/graph "a proto graph of mount states"))

View file

@ -94,7 +94,7 @@ the same with or without Mount: there are no extra mental steps.
### 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 boundaries".
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.
@ -146,13 +146,9 @@ no "ceremony".
Mount uses namespaces and vars where Component uses records and protocols.
Component manages records and protocols,
and in order to do that it requires a whole app buy-in,
which makes it a _framework_.
Component manages protocols and records, and in order to do that it requires a whole app buyin, which makes it a _framework_.
Mount does not need to manage namespaces and vars,
since it is very well managed by the Clojure Compiler,
which makes it a _library_.
Mount does not need to manage namespaces and vars, since it is very well managed by the Clojure Compiler, which makes it a _library_.
## What Component does better
@ -160,9 +156,9 @@ which makes it a _library_.
With Component multiple separate systems can be started _in the same Clojure runtime_ with different settings. Which _might_ be useful for testing, i.e. if you need to have `dev db` and `test db` started in the _same_ REPL, to _run tests within the same REPL you develop in_.
Development workflows vary and tend to be a subjective / preference based more than a true recipe, but I believe it is much cleaner to run tests in the _separate_ REPL / process. Moreover run them continuously: i.e. `boot watch speak test`: this way you don't event need to look at that other REPL / terminal, Boot will _tell_ you whether the tests pass or fail after any file is changed.
Development workflows vary and tend to be a subjective / preference based more than a true recipe, but I believe it is much cleaner to run tests in the _separate_ REPL / process. Moreover run them continuesly: i.e. `boot watch speak test`: this way you don't event need to look at that other REPL / terminal, Boot will _tell_ you whether the tests pass or fail after any file is changed.
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". In practice, if we are talking about stateful external resources, there is trully only _one_ of them with a given configuration. Different configuration => different state. It's that simple.
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". In practice, if we are talking about stateful external resources, there is trully only _one_ of them with a given configuration. Different configuration => different state. It's is that simple.
Testing is not alien to Mount and it knows how to do a thing or two:

View file

@ -1,23 +0,0 @@
{
"name": "@tolitius/mount",
"version": "0.1.16",
"license": "EPL-1.0",
"homepage": "https://github.com/tolitius/mount",
"repository": {
"type": "git",
"url": "https://github.com/tolitius/mount"
},
"author": {
"name": "tolitius",
"url": "http://www.dotkam.com"
},
"files": [
"src/*"
],
"directories": {
"lib": "src"
},
"dependencies": {
"ws": "^8.16.0"
}
}

45
pom.xml
View file

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<groupId>mount</groupId>
<artifactId>mount</artifactId>
<version>0.1.23</version>
<name>mount</name>
<description>managing Clojure and ClojureScript app state since (reset)</description>
<url>https://github.com/tolitius/mount</url>
<licenses>
<license>
<name>Eclipse Public License</name>
<url>http://www.eclipse.org/legal/epl-v10.html</url>
</license>
</licenses>
<developers>
<developer>
<name>tolitius</name>
</developer>
</developers>
<scm>
<url>https://github.com/tolitius/mount</url>
<connection>scm:git:git://github.com/tolitius/mount.git</connection>
<developerConnection>scm:git:ssh://git@github.com/tolitius/mount.git</developerConnection>
<tag>HEAD</tag>
</scm>
<dependencies>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.11.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
</build>
<repositories>
<repository>
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
</repositories>
</project>

View file

@ -1,4 +1,4 @@
(defproject mount "0.1.17"
(defproject mount "0.1.12-SNAPSHOT"
:description "managing Clojure and ClojureScript app state since (reset)"
:url "https://github.com/tolitius/mount"
:license {:name "Eclipse Public License"
@ -8,12 +8,9 @@
:dependencies [] ;; for visual clarity
:tach {:test-runner-ns 'mount.test-self-host
:source-paths ["test/core"]}
:profiles {:dev {:source-paths ["dev" "dev/clj" "test/clj"]
:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.946"]; :classifier "aot"]
:dependencies [[org.clojure/clojure "1.7.0"]
[org.clojure/clojurescript "1.7.170"]; :classifier "aot"]
[datascript "0.13.3"]
[compojure "1.4.0"]
[ring/ring-jetty-adapter "1.1.0"]
@ -30,8 +27,7 @@
:plugins [[lein-cljsbuild "1.1.1"]
[lein-doo "0.1.6"]
[lein-figwheel "0.5.0-2"]
[test2junit "1.1.3"]
[lein-tach "1.0.0"]]
[test2junit "1.1.3"]]
:test2junit-output-dir ~(or (System/getenv "CIRCLE_TEST_REPORTS") "target/test2junit")

View file

@ -1,3 +0,0 @@
{:linters {:mount/defstate {:level :warning}}
:hooks {:analyze-call {mount.core/defstate hooks.defstate/defstate
mount.core/defstate! hooks.defstate/defstate}}}

View file

@ -1,39 +0,0 @@
(ns hooks.defstate
(:require [clj-kondo.hooks-api :as api]))
(defn defstate [{:keys [node]}]
(let [[n & args] (next (:children node))
[docs args] (if (string? (api/sexpr (first args)))
[(first args) (next args)]
[nil args])
m (when-let [m (first (:meta n))]
(api/sexpr m))
m (if (map? m) m {})
ks (cond-> (take 1 args)
(> (count args) 2) (conj (nth args 2)))
invalid-key (first (remove (comp (partial contains? #{:start :stop}) api/sexpr) ks))]
(cond
invalid-key
(api/reg-finding!
{:message (str "lifecycle functions can only contain `:start` and `:stop`. illegal function found: " (api/sexpr invalid-key))
:type :mount/defstate
:row (:row (meta invalid-key))
:col (:col (meta invalid-key))})
(not (contains? (set (map api/sexpr ks)) :start))
(throw (ex-info "lifecycle functions must include `:start`" {}))
((complement contains?) #{2 4} (count args))
(throw (ex-info "lifecycle functions must consist of no more than 2 pair forms: `:start` and `:stop`" {}))
(and (contains? m :on-reload) (not (contains? #{:noop :stop} (:on-reload m))))
(api/reg-finding!
{:message "metadata `:on-reload` key can only have value of `noop` or `stop`"
:type :mount/defstate
:row (:row (meta n))
:col (:col (meta n))})
:else
{:node (api/list-node
(cond-> [(api/token-node 'def) n]
docs (conj docs)
true (conj (api/list-node
(list*
(api/token-node 'do)
args)))))})))

View file

@ -1,16 +1,13 @@
(ns mount.core
#?(:clj {:clojure.tools.namespace.repl/load false}) ; prevent reloading of this ns
#?(:clj (:require [mount.tools.macro :refer [on-error throw-runtime] :as macro]
[mount.tools.macrovich :refer [deftime]]
[mount.tools.logger :refer [log]]
[clojure.set :refer [intersection]]
[clojure.string :as s])
:cljs (:require [mount.tools.macro]
:cljs (:require [mount.tools.macro :as macro]
[clojure.set :refer [intersection]]
[mount.tools.logger :refer [log]]))
#?(:cljs (:require-macros [mount.core]
[mount.tools.macro :refer [on-error throw-runtime]]
[mount.tools.macrovich :refer [deftime]])))
[mount.tools.macro :refer [if-clj on-error throw-runtime]])))
(defonce ^:private -args (atom {})) ;; mostly for command line args and external files
(defonce ^:private state-seq (atom 0))
@ -18,6 +15,10 @@
(defonce ^:private meta-state (atom {}))
(defonce ^:private running (atom {})) ;; to clean dirty states on redefs
;; supporting tools.namespace: (disable-reload!)
#?(:clj
(alter-meta! *ns* assoc ::load false)) ;; to exclude the dependency
(defn- make-state-seq [state]
(or (:order (@meta-state state))
(swap! state-seq inc)))
@ -57,6 +58,17 @@
(stop))
(swap! running dissoc state)))
#?(:clj
(defn current-state [state]
(let [{:keys [inst var]} (@meta-state state)]
(if (= @mode :cljc)
@inst
(var-get var))))
:cljs
(defn current-state [state]
(-> (@meta-state state) :inst deref)))
#?(:clj
(defn alter-state! [{:keys [var inst]} value]
(if (= @mode :cljc)
@ -98,8 +110,8 @@
:fail? false)
:f-failed)]
(log cause :error) ;; this would mostly be useful in REPL / browser console
(alter-state! current (->NotStartedState state)))
(alter-state! current (->NotStartedState state))) ;; (!) if a state does not have :stop when _should_ this might leak
(alter-state! current (NotStartedState. state)))
(alter-state! current (NotStartedState. state))) ;; (!) if a state does not have :stop when _should_ this might leak
(swap! running dissoc state)
(update-meta! [state :status] #{:stopped})))
@ -112,12 +124,9 @@
(#?(:clj deref
:cljs -deref)
[_]
(let [{:keys [status var inst] :as state} (@meta-state name)]
(let [{:keys [status inst] :as state} (@meta-state name)]
(when-not (:started status)
(if (= :throw (-> var meta :on-lazy-start))
(throw-runtime (str ":on-lazy-start is set to :throw i.e. (defstate {:on-lazy-start :throw} " name "...) "
"and " name " state was not explicitly started before it was deref'ed (i.e. @" name ")"))
(up name state (atom #{}))))
(up name state (atom #{})))
@inst))
#?(:clj clojure.lang.IPending
:cljs IPending)
@ -126,17 +135,6 @@
[_]
(boolean ((running-states) name))))
#?(:clj
(defn current-state [state]
(let [{:keys [var]} (@meta-state state)]
(if (= @mode :cljc)
(->DerefableState state)
(var-get var))))
:cljs
(defn current-state [state]
(-> (@meta-state state) :inst deref)))
(defn on-reload-meta [s-var]
(or (-> s-var meta :on-reload)
:restart)) ;; restart by default on ns reload
@ -151,7 +149,7 @@
;;TODO: make private after figuring out the inconsistency betwen cljs compile stages
;; (i.e. _sometimes_ this, if private, is not seen by expanded "defmacro" on cljs side)
(defn mount-it [s-var s-name s-meta]
(let [with-inst (assoc s-meta :inst (atom (->NotStartedState s-name))
(let [with-inst (assoc s-meta :inst (atom (NotStartedState. s-name))
:var s-var)
on-reload (on-reload-meta s-var)
existing? (when-not (= :noop on-reload)
@ -161,42 +159,39 @@
(log (str ">> starting.. " s-name " (namespace was recompiled)"))
(up s-name with-inst (atom #{})))))
(deftime
#?(:clj
(defmacro defstate
"Defines a state. Restarts on recompilation.
Pass ^{:on-reload :noop} to prevent auto-restart
on ns recompilation, or :stop to stop on recompilation."
[state & body]
(let [[state params] (macro/name-with-attributes state body)
{:keys [start stop] :as lifecycle} (apply hash-map params)
state-name (with-ns *ns* state)
order (make-state-seq state-name)]
(validate lifecycle)
(let [s-meta (cond-> {:order order
:start `(fn [] ~start)
:status #{:stopped}}
stop (assoc :stop `(fn [] ~stop)))]
`(do
;; (log (str "|| mounting... " ~state-name))
;; only create/redefine a new state iff this is not a running ^{:on-reload :noop}
(if-not (running-noop? ~state-name)
(do
(~'defonce ~state (DerefableState. ~state-name))
(mount-it (~'var ~state) ~state-name ~s-meta))
(~'defonce ~state (current-state ~state-name)))
(~'var ~state))))))
(defmacro defstate
"defines a state (a.k.a. a stateful component).
restarts on recompilation.
pass ^{:on-reload :noop} to prevent auto-restart on ns recompilation,
or ^{:on-reload :stop} to stop on recompilation."
[state & body]
(let [[state params] (mount.tools.macro/name-with-attributes state body)
{:keys [start stop] :as lifecycle} (apply hash-map params)
state-name (with-ns *ns* state)
order (make-state-seq state-name)]
(validate lifecycle)
(let [s-meta (cond-> {:order order
:start `(fn [] ~start)
:status #{:stopped}}
stop (assoc :stop `(fn [] ~stop)))]
`(do
;; (log (str "|| mounting... " ~state-name))
;; only create/redefine a new state iff this is not a running ^{:on-reload :noop}
(if-not (running-noop? ~state-name)
(do
(~'defonce ~state (->DerefableState ~state-name))
(mount-it (~'var ~state) ~state-name ~s-meta))
(~'defonce ~state (current-state ~state-name)))
(~'var ~state)))))
(defmacro defstate! [state & {:keys [start! stop!]}]
(let [state-name (with-ns *ns* state)]
`(defstate ~state
:start (~'let [~state (mount.core/current-state ~state-name)]
~start!)
:stop (~'let [~state (mount.core/current-state ~state-name)]
~stop!))))
)
#?(:clj
(defmacro defstate! [state & {:keys [start! stop!]}]
(let [state-name (with-ns *ns* state)]
`(defstate ~state
:start (~'let [~state (mount/current-state ~state-name)]
~start!)
:stop (~'let [~state (mount/current-state ~state-name)]
~stop!)))))
(defn in-cljc-mode []
(reset! mode :cljc))
@ -284,7 +279,7 @@
(if-not (empty? fs) ;; (mount/start) vs. (mount/start #{}) vs. (mount/start #{1 2 3})
(apply start fs)
{:started #{}})
(let [states (or (->> states (map var-to-str) seq)
(let [states (or (seq states)
(all-without-subs))]
{:started (bring states up <)}))))
@ -294,7 +289,7 @@
(if-not (empty? fs) ;; (mount/stop) vs. (mount/stop #{}) vs. (mount/stop #{1 2 3})
(apply stop fs)
{:stopped #{}})
(let [states (or (->> states (map var-to-str) seq)
(let [states (or (seq states)
(find-all-states))
_ (dorun (map unsub states)) ;; unmark substitutions marked by "start-with" / "swap-states"
stopped (bring states down >)]
@ -308,8 +303,8 @@
set))
(defn only
([these]
(only (find-all-states) these))
([states]
(only (find-all-states) states))
([states these]
(intersection (mapset var-to-str these)
(mapset var-to-str states))))
@ -322,8 +317,8 @@
states))
(defn except
([these]
(except (find-all-states) these))
([states]
(except (find-all-states) states))
([states these]
(remove (mapset var-to-str these)
(mapset var-to-str states))))

View file

@ -3,10 +3,10 @@
(:import [goog.debug Console])]))
#?(:cljs
(defonce ^:dynamic *logger*
(defonce *logger*
(do
(.setCapturing (Console.) true)
(glog/getLogger "mount" nil))))
(glog/getLogger "mount"))))
#?(:clj
(defn log [msg & _]
@ -15,6 +15,6 @@
#?(:cljs
(defn log [msg & level]
(case (first level)
:error (glog/error *logger* msg nil)
(glog/info *logger* msg nil))))
:error (glog/error *logger* msg)
(glog/info *logger* msg))))

View file

@ -1,28 +1,31 @@
(ns mount.tools.macro
(:refer-clojure :exclude [case])
#?(:cljs (:require-macros [mount.tools.macrovich :refer [deftime]])
:clj (:require [mount.tools.macrovich :refer [deftime]])))
#?(:cljs (:require-macros [mount.tools.macro])))
(deftime
#?(:clj
(defmacro if-clj [then else]
(if (-> &env :ns not)
then
else)))
(defmacro on-error [msg f & {:keys [fail?]
:or {fail? true}}]
`(mount.tools.macrovich/case
:clj (try ~f
(catch Throwable t#
(if ~fail?
(throw (RuntimeException. ~msg t#))
{:f-failed (ex-info ~msg {} t#)})))
:cljs (try ~f
(catch :default t#
(if ~fail?
(throw (js/Error (~'str ~msg " " t#)))
{:f-failed (ex-info ~msg {} t#)})))))
#?(:clj
(defmacro on-error [msg f & {:keys [fail?]
:or {fail? true}}]
`(if-clj
(try ~f
(catch Throwable t#
(if ~fail?
(throw (RuntimeException. ~msg t#))
{:f-failed (ex-info ~msg {} t#)})))
(try ~f
(catch :default t#
(if ~fail?
(throw (~'str ~msg " " t#))
{:f-failed (ex-info ~msg {} t#)}))))))
(defmacro throw-runtime [msg]
`(throw (mount.tools.macrovich/case :clj (RuntimeException. ~msg) :cljs (~'str ~msg))))
)
#?(:clj
(defmacro throw-runtime [msg]
`(throw (if-clj (RuntimeException. ~msg)
(~'str ~msg)))))
;; this is a one to one copy from https://github.com/clojure/tools.macro
;; to avoid a lib dependency for a single function

View file

@ -1,21 +0,0 @@
(ns ^{:doc "From https://github.com/cgrand/macrovich. Licensed under EPL v1.
Copyright Cristophe Grand." }
mount.tools.macrovich
(:refer-clojure :exclude [case]))
(defmacro deftime
"This block will only be evaluated at the correct time for macro definition, at other times its content
are removed.
For Clojure it always behaves like a `do` block.
For Clojurescript/JVM the block is only visible to Clojure.
For self-hosted Clojurescript the block is only visible when defining macros in the pseudo-namespace."
[& body]
(when #?(:clj (not (:ns &env)) :cljs (re-matches #".*\$macros" (name (ns-name *ns*))))
`(do ~@body)))
(defmacro case [& {:keys [cljs clj]}]
(if (contains? &env '&env)
`(if (:ns ~'&env) ~cljs ~clj)
(if #?(:clj (:ns &env) :cljs true)
cljs
clj)))

View file

@ -6,9 +6,8 @@
(defn entity [conn id]
(d/entity (d/db conn) id))
(defn touch
(defn touch [conn results]
"takes 'entity ids' results from a query
e.g. '#{[272678883689461] [272678883689462] [272678883689459] [272678883689457]}'"
[conn results]
(let [e (partial entity conn)]
(map #(-> % first e d/touch) results)))

View file

@ -109,16 +109,6 @@
(is (= 42 (dval conn)))
(mount/stop)))
(testing "swap-states should swap states on start and rollback on stop"
(let [states (swap-states {#'tapp.nyse/conn swap-conn})]
(is (= states (#'mount.core/find-all-states)))
(mount/start #'tapp.nyse/conn)
(is (= 42 (dval conn)))
(mount/stop #'tapp.nyse/conn)
(mount/start #'tapp.nyse/conn)
(is (instance? datomic.peer.LocalConnection (dval conn)))
(mount/stop)))
(testing "swap-states should swap states with states and return only states that it is given"
(let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.nyse/conn}
states (swap-states t-states {#'tapp.nyse/conn swap-conn

View file

@ -1,29 +0,0 @@
(ns mount.test-self-host
(:require
[cljs.test :as t]
mount.test.fun-with-values
mount.test.private-fun
mount.test.printing
mount.test.parts
mount.test.cleanup-dirty-states
mount.test.stop-except
mount.test.start-without
mount.test.start-with
mount.test.start-with-states
))
(t/run-tests
'mount.test.fun-with-values
'mount.test.private-fun
'mount.test.printing
'mount.test.parts
'mount.test.cleanup-dirty-states
;; 'mount.test.stop-except ;; TODO: can't run with deps.edn (due to "WebSocket is not defined")
;; 'mount.test.start-with ;; boot, lein have no problems
;; 'mount.test.start-with-states ;; most likely somm misconfigured in node..
'mount.test.start-without
)
(defn run-tests []
(t/run-all-tests #"mount.test.*"))