feat: run the real clojure.tools.logging (defmacro/syntax-quote/ns + host shims)
Pivot from a jolt reimplementation to running the upstream library verbatim.
Vendors the real clojure/tools/logging.clj; jolt provides the backend and the
host primitives it needs. Language features (broadly useful for real Clojure
libs), all covered in 3-mode conformance + spec suites:
- defmacro: multi-arity dispatch (jolt-q8l) and a docstring + attr-map + params
head (jolt-qnr) — the 4-arity log macro and every level macro need these.
- syntax-quote resolves an alias-qualified symbol to its target ns (jolt-9av),
so a macro template (impl/get-logger) resolves at the use site.
- the ns macro unwraps ^{:map} metadata on the ns name (jolt-8w2 workaround,
matching def/defn/defmacro).
- a namespace object self-evaluates, so ~*ns* can be spliced into a template.
Host shims (ported from / modeled on clojure where applicable):
- clojure.string/trim-newline (ported, CharSequence interop -> count/subs)
- agent/send-off/send (minimal synchronous stubs; jolt has no thread pool/STM)
- clojure.lang.LockingTransaction/isRunning -> false
- a minimal clojure.pprint (pprint/with-pprint-dispatch/code-dispatch, for spy)
- clojure.tools.logging.impl: a jolt stderr LoggerFactory backend (the library's
designed pluggable extension point)
docs/libraries.md lists tools.logging; grammar.ebnf metadata note clarified.
Conformance 355/355 x3 modes; full jpm test gate green.
This commit is contained in:
parent
72e36f46de
commit
cf1fdfdb24
14 changed files with 556 additions and 95 deletions
|
|
@ -123,9 +123,12 @@ deref = "@" , form ; (* (deref form) *)
|
||||||
metadata = "^" , meta-form , ws , form ; (* attach metadata to form *)
|
metadata = "^" , meta-form , ws , form ; (* attach metadata to form *)
|
||||||
meta-form = map | keyword | symbol | string ;
|
meta-form = map | keyword | symbol | string ;
|
||||||
(* Normalized like Clojure: a symbol or string is a type hint -> {:tag ...};
|
(* Normalized like Clojure: a symbol or string is a type hint -> {:tag ...};
|
||||||
a keyword -> {keyword true}; a map is used as-is. On a symbol the metadata
|
a keyword -> {keyword true}; a map is used as-is. A keyword/symbol/string
|
||||||
rides on the symbol (it stays a bare symbol, so a hint like ^String is
|
meta-form on a symbol rides ON the symbol (it stays a bare symbol, so a hint
|
||||||
transparent in params/lets/bodies); other targets use a runtime with-meta. *)
|
like ^String is transparent in params/lets/bodies). A MAP meta-form routes
|
||||||
|
through a runtime (with-meta form ...) even on a symbol (jolt-8w2), so a name
|
||||||
|
with ^{:map} metadata reads as a form, not a bare symbol — def/defn/defmacro/ns
|
||||||
|
unwrap that to the bare name (and attach the metadata). *)
|
||||||
|
|
||||||
(* --------------------------------------------------------------------------
|
(* --------------------------------------------------------------------------
|
||||||
Dispatch forms — introduced by "#".
|
Dispatch forms — introduced by "#".
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,13 @@ Libraries confirmed to load and pass their conformance checks on Jolt
|
||||||
[jolt-lang/db](https://github.com/jolt-lang/db) (`next.jdbc`, `next.jdbc.sql`,
|
[jolt-lang/db](https://github.com/jolt-lang/db) (`next.jdbc`, `next.jdbc.sql`,
|
||||||
`next.jdbc.prepare`, `next.jdbc.transaction`) over `jdbc.core`, for libraries
|
`next.jdbc.prepare`, `next.jdbc.transaction`) over `jdbc.core`, for libraries
|
||||||
that target the next.jdbc API
|
that target the next.jdbc API
|
||||||
|
* [tools.logging](https://github.com/clojure/tools.logging) — the real
|
||||||
|
`clojure.tools.logging` source runs verbatim. jolt provides a native
|
||||||
|
`clojure.tools.logging.impl` backend (a stderr `LoggerFactory` — the library's
|
||||||
|
designed extension point, where slf4j/log4j/jul adapters normally plug in) plus
|
||||||
|
the host shims it needs (`agent`/`send-off`, `clojure.lang.LockingTransaction`,
|
||||||
|
a `clojure.pprint` subset, `clojure.string/trim-newline`). The level macros,
|
||||||
|
`logf`/`logp`, `spy`, and `enabled?` all work; output goes to stderr.
|
||||||
* [migratus](https://github.com/yogthos/migratus) — database migrations; loads
|
* [migratus](https://github.com/yogthos/migratus) — database migrations; loads
|
||||||
unmodified and runs filesystem SQL/EDN migrations against SQLite through the
|
unmodified and runs filesystem SQL/EDN migrations against SQLite through the
|
||||||
next.jdbc layer above. `migrate`/`rollback` round-trip end to end. Caveat:
|
next.jdbc layer above. `migrate`/`rollback` round-trip end to end. Caveat:
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,12 @@
|
||||||
;; then. Its body resolves fn/map/reduce/cond at EXPANSION time, by which point all
|
;; then. Its body resolves fn/map/reduce/cond at EXPANSION time, by which point all
|
||||||
;; of 00-syntax has loaded, so using them here is fine.
|
;; of 00-syntax has loaded, so using them here is fine.
|
||||||
(defmacro ns [nm & clauses]
|
(defmacro ns [nm & clauses]
|
||||||
(let [calls (reduce
|
;; ^{:map} metadata on the ns name reads as a (with-meta sym {...}) form, not an
|
||||||
|
;; annotated symbol (jolt-8w2). Real libraries put :author/:doc there
|
||||||
|
;; (clojure.tools.logging), so unwrap to the bare symbol; jolt does not track
|
||||||
|
;; namespace metadata, so the map is dropped.
|
||||||
|
(let [nm (if (and (seq? nm) (= 'with-meta (first nm))) (second nm) nm)
|
||||||
|
calls (reduce
|
||||||
(fn [acc clause]
|
(fn [acc clause]
|
||||||
(if (seq? clause)
|
(if (seq? clause)
|
||||||
(let [head (first clause) args (rest clause)]
|
(let [head (first clause) args (rest clause)]
|
||||||
|
|
|
||||||
|
|
@ -177,3 +177,30 @@
|
||||||
(defmethod print-method :jolt/chan [c w]
|
(defmethod print-method :jolt/chan [c w]
|
||||||
(.write w "#<channel>")
|
(.write w "#<channel>")
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
|
;; Minimal synchronous agent shim. jolt has no thread pool or STM, so this is
|
||||||
|
;; enough for libraries that hold an agent but don't depend on asynchronous
|
||||||
|
;; dispatch (e.g. clojure.tools.logging's *logging-agent*, which only sends from
|
||||||
|
;; within a transaction — never the case here, so it always logs directly). An
|
||||||
|
;; agent is an atom; send/send-off apply the action immediately. NOT concurrent.
|
||||||
|
(defn agent
|
||||||
|
"Creates an agent (an atom on jolt — synchronous, no async dispatch)."
|
||||||
|
[state & _opts]
|
||||||
|
(atom state))
|
||||||
|
|
||||||
|
(defn send-off
|
||||||
|
"Apply (action state & args) to the agent's state immediately; return the agent."
|
||||||
|
[a f & args]
|
||||||
|
(apply swap! a f args)
|
||||||
|
a)
|
||||||
|
|
||||||
|
(defn send
|
||||||
|
"Like send-off on jolt (no separate thread pool)."
|
||||||
|
[a f & args]
|
||||||
|
(apply swap! a f args)
|
||||||
|
a)
|
||||||
|
|
||||||
|
(defn agent-error
|
||||||
|
"jolt agents never enter an error state."
|
||||||
|
[_a]
|
||||||
|
nil)
|
||||||
|
|
|
||||||
28
src/jolt/clojure/pprint.clj
Normal file
28
src/jolt/clojure/pprint.clj
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
;; clojure.pprint — minimal jolt shim.
|
||||||
|
;;
|
||||||
|
;; The real clojure.pprint is a full pretty-printer (thousands of lines of column
|
||||||
|
;; tracking / dispatch tables). This shim provides just the surface that portable
|
||||||
|
;; libraries reach for when they "pretty print" a value — notably
|
||||||
|
;; clojure.tools.logging/spy (pprint + with-pprint-dispatch + code-dispatch).
|
||||||
|
;; pprint writes the value readably with a trailing newline (no pretty layout);
|
||||||
|
;; the dispatch vars are recognized but not used for layout.
|
||||||
|
(ns clojure.pprint)
|
||||||
|
|
||||||
|
(def code-dispatch
|
||||||
|
"Recognized but not used for layout on jolt." :code-dispatch)
|
||||||
|
(def simple-dispatch
|
||||||
|
"Recognized but not used for layout on jolt." :simple-dispatch)
|
||||||
|
|
||||||
|
(def ^:dynamic *print-pprint-dispatch* simple-dispatch)
|
||||||
|
|
||||||
|
(defn pprint
|
||||||
|
"Print object readably followed by a newline. Writes to *out* (or the given
|
||||||
|
writer). Not a pretty-printer on jolt — no column-aware layout."
|
||||||
|
([object] (prn object))
|
||||||
|
([object writer] (binding [*out* writer] (prn object))))
|
||||||
|
|
||||||
|
(defmacro with-pprint-dispatch
|
||||||
|
"Evaluate body with the given pprint dispatch selected. On jolt the dispatch is
|
||||||
|
recognized but does not affect layout, so this just evaluates body."
|
||||||
|
[_dispatch & body]
|
||||||
|
`(do ~@body))
|
||||||
|
|
@ -135,3 +135,17 @@
|
||||||
(let [c (str ch)]
|
(let [c (str ch)]
|
||||||
(if (or (= c "\\") (= c "$")) (str "\\" c) c)))
|
(if (or (= c "\\") (= c "$")) (str "\\" c) c)))
|
||||||
(seq replacement))))
|
(seq replacement))))
|
||||||
|
|
||||||
|
;; Ported from clojure.string/trim-newline (CharSequence interop replaced with
|
||||||
|
;; portable count/subs). Removes all trailing \n or \r characters.
|
||||||
|
(defn trim-newline
|
||||||
|
"Removes all trailing newline \\n or return \\r characters from
|
||||||
|
string. Similar to Perl's chomp."
|
||||||
|
[s]
|
||||||
|
(loop [index (count s)]
|
||||||
|
(if (zero? index)
|
||||||
|
""
|
||||||
|
(let [c (subs s (dec index) index)]
|
||||||
|
(if (or (= c "\n") (= c "\r"))
|
||||||
|
(recur (dec index))
|
||||||
|
(subs s 0 index))))))
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,338 @@
|
||||||
;; clojure.tools.logging — jolt shim of the public API.
|
;;; logging.clj -- delegated logging for Clojure
|
||||||
;;
|
|
||||||
;; Faithful to the upstream macro surface (logp/logf, the level macros and their
|
;; by Alex Taggart
|
||||||
;; f-variants, log, enabled?, spy), dispatching through the Logger/LoggerFactory
|
;; July 27, 2009
|
||||||
;; protocols in clojure.tools.logging.impl. Two deliberate departures from
|
|
||||||
;; upstream, both forced by jolt not being a JVM:
|
;; Copyright (c) Alex Taggart, July 2009. All rights reserved. The use
|
||||||
;;
|
;; and distribution terms for this software are covered by the Eclipse
|
||||||
;; - log* writes directly. The real log* may hand off to an agent when inside a
|
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
|
||||||
;; running clojure.lang.LockingTransaction; jolt has neither agents nor STM.
|
;; which can be found in the file epl-v10.html at the root of this
|
||||||
;; - The throwable-first-argument branch is dropped. Upstream splits args on
|
;; distribution. By using this software in any fashion, you are
|
||||||
;; (instance? Throwable x); on jolt that is always false for exception values,
|
;; agreeing to be bound by the terms of this license. You must not
|
||||||
;; so the would-be throwable simply becomes part of the printed message. This
|
;; remove this notice, or any other, from this software.
|
||||||
;; also avoids the double-evaluation the branch would otherwise cause here.
|
(ns ^{:author "Alex Taggart"
|
||||||
(ns clojure.tools.logging
|
:doc "Logging macros which delegate to a specific logging implementation.
|
||||||
|
|
||||||
|
A logging implementation is selected at runtime when this namespace is first
|
||||||
|
loaded. For more details, see the documentation for *logger-factory*.
|
||||||
|
|
||||||
|
If you want to test that your code emits specific log messages, see the
|
||||||
|
clojure.tools.logging.test namespace."}
|
||||||
|
clojure.tools.logging
|
||||||
|
(:use
|
||||||
|
[clojure.string :only [trim-newline]]
|
||||||
|
[clojure.pprint :only [code-dispatch pprint with-pprint-dispatch]])
|
||||||
(:require [clojure.tools.logging.impl :as impl]))
|
(:require [clojure.tools.logging.impl :as impl]))
|
||||||
|
|
||||||
;; The LoggerFactory used to obtain loggers. Defaults to the jolt stderr
|
(def ^{:doc
|
||||||
;; factory; rebind to plug in a different backend.
|
"The default agent used for performing logging when direct logging is
|
||||||
(def ^:dynamic *logger-factory* (impl/find-factory))
|
disabled. See log* for details." :dynamic true}
|
||||||
|
*logging-agent* (agent nil :error-mode :continue))
|
||||||
|
|
||||||
|
(def ^{:doc
|
||||||
|
"The set of levels that will require using an agent when logging from within a
|
||||||
|
running transaction. Defaults to #{:info :warn}. See log* for details." :dynamic true}
|
||||||
|
*tx-agent-levels* #{:info :warn})
|
||||||
|
|
||||||
|
(def ^{:doc
|
||||||
|
"Overrides the default rules for choosing between logging directly or via an
|
||||||
|
agent. Defaults to nil. See log* for details." :dynamic true}
|
||||||
|
*force* nil)
|
||||||
|
|
||||||
(defn log*
|
(defn log*
|
||||||
"Write a log entry through the logger. Direct, unlike upstream's agent path."
|
"Attempts to log a message, either directly or via an agent; does not check if
|
||||||
|
the level is enabled.
|
||||||
|
|
||||||
|
For performance reasons, an agent will only be used when invoked within a
|
||||||
|
running transaction, and only for logging levels specified by
|
||||||
|
*tx-agent-levels*. This allows those entries to only be written once the
|
||||||
|
transaction commits, and are discarded if it is retried or aborted. As
|
||||||
|
corollary, other levels (e.g., :debug, :error) will be written even from
|
||||||
|
failed transactions though at the cost of repeat messages during retries.
|
||||||
|
|
||||||
|
One can override the above by setting *force* to :direct or :agent; all
|
||||||
|
subsequent writes will be direct or via an agent, respectively."
|
||||||
[logger level throwable message]
|
[logger level throwable message]
|
||||||
(impl/write! logger level throwable message)
|
(if (case *force*
|
||||||
|
:agent true
|
||||||
|
:direct false
|
||||||
|
(and (clojure.lang.LockingTransaction/isRunning)
|
||||||
|
(*tx-agent-levels* level)))
|
||||||
|
(send-off *logging-agent*
|
||||||
|
(fn [_#] (impl/write! logger level throwable message)))
|
||||||
|
(impl/write! logger level throwable message))
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defmacro logp
|
(declare ^{:dynamic true} *logger-factory*) ; default LoggerFactory instance for calling impl/get-logger
|
||||||
"Log a message built by print-str of args, at the given level. Args are
|
|
||||||
evaluated only when the level is enabled."
|
|
||||||
[level & args]
|
|
||||||
`(let [logger# (clojure.tools.logging.impl/get-logger *logger-factory* ~(str *ns*))]
|
|
||||||
(when (clojure.tools.logging.impl/enabled? logger# ~level)
|
|
||||||
(log* logger# ~level nil (print-str ~@args)))))
|
|
||||||
|
|
||||||
(defmacro logf
|
|
||||||
"Log a clojure.core/format message at the given level. Args are evaluated
|
|
||||||
only when the level is enabled."
|
|
||||||
[level & args]
|
|
||||||
`(let [logger# (clojure.tools.logging.impl/get-logger *logger-factory* ~(str *ns*))]
|
|
||||||
(when (clojure.tools.logging.impl/enabled? logger# ~level)
|
|
||||||
(log* logger# ~level nil (format ~@args)))))
|
|
||||||
|
|
||||||
(defmacro log
|
(defmacro log
|
||||||
([level message] `(logp ~level ~message))
|
"Evaluates and logs a message only if the specified level is enabled. See log*
|
||||||
([level throwable message] `(logp ~level ~throwable ~message)))
|
for more details."
|
||||||
|
([level message]
|
||||||
|
`(log ~level nil ~message))
|
||||||
|
([level throwable message]
|
||||||
|
`(log ~*ns* ~level ~throwable ~message))
|
||||||
|
([logger-ns level throwable message]
|
||||||
|
`(log *logger-factory* ~logger-ns ~level ~throwable ~message))
|
||||||
|
([logger-factory logger-ns level throwable message]
|
||||||
|
`(let [logger# (impl/get-logger ~logger-factory ~logger-ns)]
|
||||||
|
(if (impl/enabled? logger# ~level)
|
||||||
|
(log* logger# ~level ~throwable ~message)))))
|
||||||
|
|
||||||
(defmacro trace [& args] `(logp :trace ~@args))
|
(defmacro logp
|
||||||
(defmacro debug [& args] `(logp :debug ~@args))
|
"Logs a message using print style args. Can optionally take a throwable as its
|
||||||
(defmacro info [& args] `(logp :info ~@args))
|
second arg. See level-specific macros, e.g., debug.
|
||||||
(defmacro warn [& args] `(logp :warn ~@args))
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
(defmacro error [& args] `(logp :error ~@args))
|
{:arglists '([level message & more] [level throwable message & more])}
|
||||||
(defmacro fatal [& args] `(logp :fatal ~@args))
|
[level x & more]
|
||||||
|
(if (or (instance? String x) (nil? more)) ; optimize for common case
|
||||||
|
`(log ~level (print-str ~x ~@more))
|
||||||
|
`(let [logger# (impl/get-logger *logger-factory* ~*ns*)]
|
||||||
|
(if (impl/enabled? logger# ~level)
|
||||||
|
(let [x# ~x]
|
||||||
|
(if (instance? Throwable x#) ; type check only when enabled
|
||||||
|
(log* logger# ~level x# (print-str ~@more))
|
||||||
|
(log* logger# ~level nil (print-str x# ~@more))))))))
|
||||||
|
|
||||||
(defmacro tracef [& args] `(logf :trace ~@args))
|
(defmacro logf
|
||||||
(defmacro debugf [& args] `(logf :debug ~@args))
|
"Logs a message using a format string and args. Can optionally take a
|
||||||
(defmacro infof [& args] `(logf :info ~@args))
|
throwable as its second arg. See level-specific macros, e.g., debugf.
|
||||||
(defmacro warnf [& args] `(logf :warn ~@args))
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
(defmacro errorf [& args] `(logf :error ~@args))
|
{:arglists '([level fmt & fmt-args] [level throwable fmt & fmt-args])}
|
||||||
(defmacro fatalf [& args] `(logf :fatal ~@args))
|
[level x & more]
|
||||||
|
(if (or (instance? String x) (nil? more)) ; optimize for common case
|
||||||
|
`(log ~level (format ~x ~@more))
|
||||||
|
`(let [logger# (impl/get-logger *logger-factory* ~*ns*)]
|
||||||
|
(if (impl/enabled? logger# ~level)
|
||||||
|
(let [x# ~x]
|
||||||
|
(if (instance? Throwable x#) ; type check only when enabled
|
||||||
|
(log* logger# ~level x# (format ~@more))
|
||||||
|
(log* logger# ~level nil (format x# ~@more))))))))
|
||||||
|
|
||||||
(defmacro enabled?
|
(defmacro enabled?
|
||||||
"True if the given level is enabled for the (optionally given) logger ns."
|
"Returns true if the specific logging level is enabled. Use of this macro
|
||||||
([level] `(clojure.tools.logging.impl/enabled? (clojure.tools.logging.impl/get-logger *logger-factory* ~(str *ns*)) ~level))
|
should only be necessary if one needs to execute alternate code paths beyond
|
||||||
([level log-ns] `(clojure.tools.logging.impl/enabled? (clojure.tools.logging.impl/get-logger *logger-factory* ~log-ns) ~level)))
|
whether the log should be written to."
|
||||||
|
([level]
|
||||||
|
`(enabled? ~level ~*ns*))
|
||||||
|
([level logger-ns]
|
||||||
|
`(impl/enabled? (impl/get-logger *logger-factory* ~logger-ns) ~level)))
|
||||||
|
|
||||||
(defmacro spy
|
(defmacro spy
|
||||||
"Evaluate expr, log it at the given level (default :debug) as expr => value,
|
"Evaluates expr and may write the form and its result to the log. Returns the
|
||||||
and return the value. Single variadic arity (jolt defmacro takes only the
|
result of expr. Defaults to :debug log level."
|
||||||
first clause of a multi-arity macro), dispatching on arg count."
|
([expr]
|
||||||
[& args]
|
`(spy :debug ~expr))
|
||||||
(let [level (if (= 1 (count args)) :debug (first args))
|
([level expr]
|
||||||
expr (if (= 1 (count args)) (first args) (second args))]
|
|
||||||
`(let [a# ~expr]
|
`(let [a# ~expr]
|
||||||
(logp ~level (print-str '~expr "=>" (pr-str a#)))
|
(log ~level
|
||||||
|
(let [s# (with-out-str
|
||||||
|
(with-pprint-dispatch code-dispatch ; need a better way
|
||||||
|
(pprint '~expr)
|
||||||
|
(print "=> ")
|
||||||
|
(pprint a#)))]
|
||||||
|
(trim-newline s#)))
|
||||||
a#)))
|
a#)))
|
||||||
|
|
||||||
|
(defmacro spyf
|
||||||
|
"Evaluates expr and may write (format fmt result) to the log. Returns the
|
||||||
|
result of expr. Defaults to :debug log level.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
([fmt expr]
|
||||||
|
`(spyf :debug ~fmt ~expr))
|
||||||
|
([level fmt expr]
|
||||||
|
`(let [a# ~expr]
|
||||||
|
(log ~level (format ~fmt a#))
|
||||||
|
a#)))
|
||||||
|
|
||||||
|
(defn log-stream
|
||||||
|
"Creates a PrintStream that will output to the log at the specified level."
|
||||||
|
[level logger-ns]
|
||||||
|
(let [logger (impl/get-logger *logger-factory* logger-ns)]
|
||||||
|
(java.io.PrintStream.
|
||||||
|
(proxy [java.io.ByteArrayOutputStream] []
|
||||||
|
(flush []
|
||||||
|
; deal with reflection in proxy-super
|
||||||
|
(let [^java.io.ByteArrayOutputStream this this]
|
||||||
|
(proxy-super flush)
|
||||||
|
(let [message (.trim (.toString this))]
|
||||||
|
(proxy-super reset)
|
||||||
|
(if (> (.length message) 0)
|
||||||
|
(log* logger level nil message))))))
|
||||||
|
true)))
|
||||||
|
|
||||||
|
(let [orig (atom nil) ; holds original System.out and System.err
|
||||||
|
monitor (Object.)] ; sync monitor for calling setOut/setErr
|
||||||
|
(defn log-capture!
|
||||||
|
"Captures System.out and System.err, piping all writes of those streams to
|
||||||
|
the log. If unspecified, levels default to :info and :error, respectively.
|
||||||
|
The specified logger-ns value will be used to namespace all log entries.
|
||||||
|
|
||||||
|
Note: use with-logs to redirect output of *out* or *err*.
|
||||||
|
|
||||||
|
Warning: if the logging implementation is configured to output to System.out
|
||||||
|
(as is the default with java.util.logging) then using this function will
|
||||||
|
result in StackOverflowException when writing to the log."
|
||||||
|
; Implementation Notes:
|
||||||
|
; - only set orig when nil to preserve original out/err
|
||||||
|
; - no enabled? check before making streams since that may change later
|
||||||
|
([logger-ns]
|
||||||
|
(log-capture! logger-ns :info :error))
|
||||||
|
([logger-ns out-level err-level]
|
||||||
|
(locking monitor
|
||||||
|
(compare-and-set! orig nil [System/out System/err])
|
||||||
|
(System/setOut (log-stream out-level logger-ns))
|
||||||
|
(System/setErr (log-stream err-level logger-ns)))))
|
||||||
|
(defn log-uncapture!
|
||||||
|
"Restores System.out and System.err to their original values."
|
||||||
|
[]
|
||||||
|
(locking monitor
|
||||||
|
(when-let [[out err :as v] @orig]
|
||||||
|
(swap! orig (constantly nil))
|
||||||
|
(System/setOut out)
|
||||||
|
(System/setErr err)))))
|
||||||
|
|
||||||
|
(defmacro with-logs
|
||||||
|
"Evaluates exprs in a context in which *out* and *err* write to the log. The
|
||||||
|
specified logger-ns value will be used to namespace all log entries.
|
||||||
|
|
||||||
|
By default *out* and *err* write to :info and :error, respectively."
|
||||||
|
{:arglists '([logger-ns & body]
|
||||||
|
[[logger-ns out-level err-level] & body])}
|
||||||
|
[arg & body]
|
||||||
|
; Implementation Notes:
|
||||||
|
; - no enabled? check before making writers since that may change later
|
||||||
|
(let [[logger-ns out-level err-level] (if (vector? arg)
|
||||||
|
arg
|
||||||
|
[arg :info :error])]
|
||||||
|
`(binding [*out* (java.io.OutputStreamWriter.
|
||||||
|
(log-stream ~out-level ~logger-ns))
|
||||||
|
*err* (java.io.OutputStreamWriter.
|
||||||
|
(log-stream ~err-level ~logger-ns))]
|
||||||
|
~@body)))
|
||||||
|
|
||||||
|
;; level-specific macros
|
||||||
|
|
||||||
|
(defmacro trace
|
||||||
|
"Trace level logging using print-style args.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([message & more] [throwable message & more])}
|
||||||
|
[& args]
|
||||||
|
`(logp :trace ~@args))
|
||||||
|
|
||||||
|
(defmacro debug
|
||||||
|
"Debug level logging using print-style args.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([message & more] [throwable message & more])}
|
||||||
|
[& args]
|
||||||
|
`(logp :debug ~@args))
|
||||||
|
|
||||||
|
(defmacro info
|
||||||
|
"Info level logging using print-style args.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([message & more] [throwable message & more])}
|
||||||
|
[& args]
|
||||||
|
`(logp :info ~@args))
|
||||||
|
|
||||||
|
(defmacro warn
|
||||||
|
"Warn level logging using print-style args.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([message & more] [throwable message & more])}
|
||||||
|
[& args]
|
||||||
|
`(logp :warn ~@args))
|
||||||
|
|
||||||
|
(defmacro error
|
||||||
|
"Error level logging using print-style args.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([message & more] [throwable message & more])}
|
||||||
|
[& args]
|
||||||
|
`(logp :error ~@args))
|
||||||
|
|
||||||
|
(defmacro fatal
|
||||||
|
"Fatal level logging using print-style args.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([message & more] [throwable message & more])}
|
||||||
|
[& args]
|
||||||
|
`(logp :fatal ~@args))
|
||||||
|
|
||||||
|
(defmacro tracef
|
||||||
|
"Trace level logging using format.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])}
|
||||||
|
[& args]
|
||||||
|
`(logf :trace ~@args))
|
||||||
|
|
||||||
|
(defmacro debugf
|
||||||
|
"Debug level logging using format.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])}
|
||||||
|
[& args]
|
||||||
|
`(logf :debug ~@args))
|
||||||
|
|
||||||
|
(defmacro infof
|
||||||
|
"Info level logging using format.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])}
|
||||||
|
[& args]
|
||||||
|
`(logf :info ~@args))
|
||||||
|
|
||||||
|
(defmacro warnf
|
||||||
|
"Warn level logging using format.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])}
|
||||||
|
[& args]
|
||||||
|
`(logf :warn ~@args))
|
||||||
|
|
||||||
|
(defmacro errorf
|
||||||
|
"Error level logging using format.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])}
|
||||||
|
[& args]
|
||||||
|
`(logf :error ~@args))
|
||||||
|
|
||||||
|
(defmacro fatalf
|
||||||
|
"Fatal level logging using format.
|
||||||
|
Use the 'logging.readable' namespace to avoid wrapping args in pr-str."
|
||||||
|
{:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])}
|
||||||
|
[& args]
|
||||||
|
`(logf :fatal ~@args))
|
||||||
|
|
||||||
|
(defn- call-str [str]
|
||||||
|
(let [fq-sym (symbol str)
|
||||||
|
ns-str (or (namespace fq-sym)
|
||||||
|
(throw (RuntimeException.
|
||||||
|
(format "The value of the clojure.tools.logging.factory system property is not fully-qualified: %s"
|
||||||
|
(pr-str str)))))
|
||||||
|
ns-sym (symbol ns-str)
|
||||||
|
_ (try
|
||||||
|
(require ns-sym)
|
||||||
|
(catch Exception ex
|
||||||
|
(throw (RuntimeException.
|
||||||
|
(format "Could not resolve namespace for %s. Either it does not exist or it has a (circular) dependency on clojure.tools.logging."
|
||||||
|
(pr-str str))))))
|
||||||
|
fn-sym (symbol (name fq-sym))
|
||||||
|
fn-var (ns-resolve ns-sym fn-sym)]
|
||||||
|
(if fn-var
|
||||||
|
(fn-var)
|
||||||
|
(throw (RuntimeException.
|
||||||
|
(format "Could not resolve var for %s."
|
||||||
|
(pr-str str)))))))
|
||||||
|
|
||||||
|
(defn- find-factory []
|
||||||
|
(if-let [factory-fn-str (System/getProperty "clojure.tools.logging.factory")]
|
||||||
|
(call-str factory-fn-str)
|
||||||
|
(impl/find-factory)))
|
||||||
|
|
||||||
|
(def ^:dynamic *logger-factory*
|
||||||
|
"An instance satisfying the clojure.tools.logging.impl/LoggerFactory protocol,
|
||||||
|
which allows uniform access to an underlying logging implementation.
|
||||||
|
|
||||||
|
The default value will be obtained by invoking a no-arg function named by the
|
||||||
|
\"clojure.tools.logging.factory\" system property, or if unset, by invoking
|
||||||
|
clojure.tools.logging.impl/find-factory.
|
||||||
|
|
||||||
|
After loading, this var can be programmatically changed to a different
|
||||||
|
LoggerFactory implementation via binding or alter-var-root.
|
||||||
|
|
||||||
|
See the various factory functions in clojure.tools.logger.impl."
|
||||||
|
(find-factory))
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,16 @@
|
||||||
{:jolt/type :symbol
|
{:jolt/type :symbol
|
||||||
:ns (or (get (ctx :env) :compile-ns) (ctx-current-ns ctx))
|
:ns (or (get (ctx :env) :compile-ns) (ctx-current-ns ctx))
|
||||||
:name nm}))
|
:name nm}))
|
||||||
form))
|
# Alias-qualified (impl/foo): resolve the alias to its target namespace so the
|
||||||
|
# emitted symbol resolves at the macro's USE site, which has no such alias
|
||||||
|
# (jolt-9av). Matches Clojure's syntax-quote. A real ns name (not an alias)
|
||||||
|
# has no entry and is left as written.
|
||||||
|
(let [cur (ctx-find-ns ctx (or (get (ctx :env) :compile-ns) (ctx-current-ns ctx)))
|
||||||
|
target (and cur (or (ns-alias-lookup cur (form :ns))
|
||||||
|
(ns-import-lookup cur (form :ns))))]
|
||||||
|
(if target
|
||||||
|
{:jolt/type :symbol :ns target :name (form :name)}
|
||||||
|
form))))
|
||||||
|
|
||||||
(defn- d-realize
|
(defn- d-realize
|
||||||
"Realize a lazy-seq to an array for positional destructuring / splicing; pass
|
"Realize a lazy-seq to an array for positional destructuring / splicing; pass
|
||||||
|
|
@ -1828,36 +1837,56 @@
|
||||||
"defmacro" (let [# ^{:map} metadata on the name reads as a (with-meta sym …)
|
"defmacro" (let [# ^{:map} metadata on the name reads as a (with-meta sym …)
|
||||||
# form (jolt-8w2); unwrap to the bare symbol like def does.
|
# form (jolt-8w2); unwrap to the bare symbol like def does.
|
||||||
name-sym (unwrap-meta-name (in form 1))
|
name-sym (unwrap-meta-name (in form 1))
|
||||||
rest-form (tuple/slice form 2)
|
after-name (tuple/slice form 2)
|
||||||
# optional docstring
|
# Skip an optional leading docstring (string) then an optional
|
||||||
has-doc? (and (> (length rest-form) 0) (string? (first rest-form)))
|
# attr-map (a struct that is not a symbol — a map literal reads
|
||||||
raw-args (if has-doc? (in rest-form 1) (first rest-form))
|
# as a struct), matching defn. Real macros use both, e.g.
|
||||||
raw-body (tuple/slice rest-form (if has-doc? 2 1))
|
# (defmacro info "doc" {:arglists '(...)} [& args] …).
|
||||||
# arity-clause form: (defmacro name ([params] body...)) — like
|
a1 (if (and (> (length after-name) 0) (string? (first after-name)))
|
||||||
# fn/defn. In a code form a vector literal is a tuple and a list
|
(tuple/slice after-name 1) after-name)
|
||||||
# is an array, so a params vector reads as a tuple while an arity
|
after-meta (if (and (> (length a1) 0)
|
||||||
# clause reads as a list (array): unwrap its params + body. Single
|
(struct? (first a1))
|
||||||
# arity only (what real macros use); a multi-arity macro takes the
|
(not= :symbol (get (first a1) :jolt/type)))
|
||||||
# first clause.
|
(tuple/slice a1 1) a1)
|
||||||
arity-clause? (array? raw-args)
|
# What remains is either a params VECTOR (tuple) + body, or one
|
||||||
args-form (if arity-clause? (first raw-args) raw-args)
|
# or more arity CLAUSES (each a list, i.e. a janet array). Build
|
||||||
body (if arity-clause? (tuple/slice raw-args 1) raw-body)
|
# a uniform arity list [{:params … :body …} …].
|
||||||
param-info (parse-params args-form)
|
multi? (and (> (length after-meta) 0) (array? (first after-meta)))
|
||||||
fixed-pats (param-info :fixed)
|
arities (if multi?
|
||||||
rest-pat (param-info :rest)
|
(map (fn [cl] {:params (first cl) :body (tuple/slice cl 1)})
|
||||||
|
after-meta)
|
||||||
|
@[{:params (first after-meta) :body (tuple/slice after-meta 1)}])
|
||||||
defining-ns (ctx-current-ns ctx)]
|
defining-ns (ctx-current-ns ctx)]
|
||||||
(def interp-fn (fn [& macro-args]
|
(def interp-fn (fn [& macro-args]
|
||||||
|
(def n (length macro-args))
|
||||||
|
# Pick the arity: an exact fixed-count match wins; otherwise the
|
||||||
|
# first variadic arity that accepts n args (Clojure fn dispatch).
|
||||||
|
(var chosen nil)
|
||||||
|
(each ar arities
|
||||||
|
(def pi (parse-params (ar :params)))
|
||||||
|
(when (and (nil? chosen) (not (pi :rest)) (= n (length (pi :fixed))))
|
||||||
|
(set chosen [pi (ar :body)])))
|
||||||
|
(when (nil? chosen)
|
||||||
|
(each ar arities
|
||||||
|
(def pi (parse-params (ar :params)))
|
||||||
|
(when (and (nil? chosen) (pi :rest) (>= n (length (pi :fixed))))
|
||||||
|
(set chosen [pi (ar :body)]))))
|
||||||
|
(when (nil? chosen)
|
||||||
|
(error (string "no matching arity for macro " (name-sym :name)
|
||||||
|
" (" n " args)")))
|
||||||
|
(def pi (chosen 0))
|
||||||
|
(def body (chosen 1))
|
||||||
(var new-bindings @{})
|
(var new-bindings @{})
|
||||||
(table/setproto new-bindings bindings)
|
(table/setproto new-bindings bindings)
|
||||||
(put new-bindings "&env" @{}) # implicit &env for macro bodies (table — nil-safe)
|
(put new-bindings "&env" @{}) # implicit &env for macro bodies (table — nil-safe)
|
||||||
(var i 0)
|
(var i 0)
|
||||||
# Destructure macro params (like fn), so [& [a & more :as all]]
|
# Destructure macro params (like fn), so [& [a & more :as all]]
|
||||||
# and {:keys …} rest forms work in macro arglists.
|
# and {:keys …} rest forms work in macro arglists.
|
||||||
(each pat fixed-pats
|
(each pat (pi :fixed)
|
||||||
(destructure-bind ctx new-bindings pat (macro-args i))
|
(destructure-bind ctx new-bindings pat (macro-args i))
|
||||||
(++ i))
|
(++ i))
|
||||||
(when rest-pat
|
(when (pi :rest)
|
||||||
(destructure-bind ctx new-bindings rest-pat (rest-args-val macro-args i)))
|
(destructure-bind ctx new-bindings (pi :rest) (rest-args-val macro-args i)))
|
||||||
# Use defining namespace for symbol resolution
|
# Use defining namespace for symbol resolution
|
||||||
(def saved-ns (ctx-current-ns ctx))
|
(def saved-ns (ctx-current-ns ctx))
|
||||||
(ctx-set-current-ns ctx defining-ns)
|
(ctx-set-current-ns ctx defining-ns)
|
||||||
|
|
@ -1870,12 +1899,20 @@
|
||||||
(set result (eval-form ctx new-bindings bf)))
|
(set result (eval-form ctx new-bindings bf)))
|
||||||
(ctx-set-current-ns ctx saved-ns)
|
(ctx-set-current-ns ctx saved-ns)
|
||||||
result))
|
result))
|
||||||
# Prefer a COMPILED expander (native-speed expansion, zero runtime
|
# A COMPILED expander (native-speed) is only built for the
|
||||||
# cost). Skip when the body uses &env/&form (the compiled fn has no
|
# single-arity case (the compile hook + recompile path take one
|
||||||
# such params) — those fall back to the interpreted closure.
|
# [args body]); multi-arity macros use the interpreted expander.
|
||||||
(def uses-env (or (form-uses-sym? body "&env") (form-uses-sym? body "&form")))
|
(def single? (= 1 (length arities)))
|
||||||
|
(def args-form (and single? ((first arities) :params)))
|
||||||
|
(def body (and single? ((first arities) :body)))
|
||||||
|
(def uses-env (do (var u false)
|
||||||
|
(each ar arities
|
||||||
|
(when (or (form-uses-sym? (ar :body) "&env")
|
||||||
|
(form-uses-sym? (ar :body) "&form"))
|
||||||
|
(set u true)))
|
||||||
|
u))
|
||||||
(def compiled-fn
|
(def compiled-fn
|
||||||
(when (and macro-compile-hook (not uses-env))
|
(when (and macro-compile-hook single? (not uses-env))
|
||||||
(macro-compile-hook ctx args-form body)))
|
(macro-compile-hook ctx args-form body)))
|
||||||
(def macro-fn (or compiled-fn interp-fn))
|
(def macro-fn (or compiled-fn interp-fn))
|
||||||
(let [ns-name (ctx-current-ns ctx)
|
(let [ns-name (ctx-current-ns ctx)
|
||||||
|
|
@ -1886,8 +1923,10 @@
|
||||||
# compile it once the analyzer is alive (staged bootstrap): a
|
# compile it once the analyzer is alive (staged bootstrap): a
|
||||||
# macro defined WHILE the analyzer is still being built gets an
|
# macro defined WHILE the analyzer is still being built gets an
|
||||||
# interpreted closure now, a compiled expander later. uses-env
|
# interpreted closure now, a compiled expander later. uses-env
|
||||||
# macros stay interpreted (the compiled fn* has no &env/&form).
|
# macros stay interpreted (the compiled fn* has no &env/&form);
|
||||||
(put v :macro-src @[args-form body])
|
# multi-arity macros keep the interpreted dispatch (no single
|
||||||
|
# [args body] to recompile).
|
||||||
|
(when single? (put v :macro-src @[args-form body]))
|
||||||
(put v :macro-uses-env uses-env)
|
(put v :macro-uses-env uses-env)
|
||||||
(when compiled-fn (put v :macro-compiled true))
|
(when compiled-fn (put v :macro-compiled true))
|
||||||
# A (re)defined macro invalidates any cached expansions.
|
# A (re)defined macro invalidates any cached expansions.
|
||||||
|
|
@ -2412,8 +2451,11 @@
|
||||||
(if (= :jolt/char (form :jolt/type))
|
(if (= :jolt/char (form :jolt/type))
|
||||||
form
|
form
|
||||||
# a UUID/inst value flowing back through eval (macro expansion, eval of a
|
# a UUID/inst value flowing back through eval (macro expansion, eval of a
|
||||||
# read form) is a self-evaluating literal, like chars.
|
# read form) is a self-evaluating literal, like chars. A namespace object
|
||||||
(if (or (= :jolt/uuid (form :jolt/type)) (= :jolt/inst (form :jolt/type)))
|
# does too: `~*ns*` in a syntax-quote (clojure.tools.logging) splices the
|
||||||
|
# live ns into the expansion.
|
||||||
|
(if (or (= :jolt/uuid (form :jolt/type)) (= :jolt/inst (form :jolt/type))
|
||||||
|
(= :jolt/namespace (form :jolt/type)))
|
||||||
form
|
form
|
||||||
(if (= :jolt/set (form :jolt/type))
|
(if (= :jolt/set (form :jolt/type))
|
||||||
# evaluate each element (set literals like #{(inc 1)} must compute)
|
# evaluate each element (set literals like #{(inc 1)} must compute)
|
||||||
|
|
|
||||||
|
|
@ -542,6 +542,9 @@
|
||||||
# (resources/<dir>). getSystemClassLoader yields the same stub.
|
# (resources/<dir>). getSystemClassLoader yields the same stub.
|
||||||
(each nm ["ClassLoader" "java.lang.ClassLoader"]
|
(each nm ["ClassLoader" "java.lang.ClassLoader"]
|
||||||
(register-class-statics! nm @{"getSystemClassLoader" (fn [] @{:jolt/type :jolt/classloader})}))
|
(register-class-statics! nm @{"getSystemClassLoader" (fn [] @{:jolt/type :jolt/classloader})}))
|
||||||
|
# No STM on jolt: a transaction is never running, so logging libraries that
|
||||||
|
# gate agent-vs-direct on it (clojure.tools.logging/log*) always log directly.
|
||||||
|
(register-class-statics! "clojure.lang.LockingTransaction" @{"isRunning" (fn [] false)})
|
||||||
(register-tagged-methods! :jolt/classloader
|
(register-tagged-methods! :jolt/classloader
|
||||||
@{"getResource" (fn [self path] nil)
|
@{"getResource" (fn [self path] nil)
|
||||||
"getResources" (fn [self path] nil)
|
"getResources" (fn [self path] nil)
|
||||||
|
|
|
||||||
|
|
@ -495,6 +495,25 @@
|
||||||
# ns (jolt-9pu — it used to see an empty table).
|
# ns (jolt-9pu — it used to see an empty table).
|
||||||
["cross-ns methods visible" "[:sql]"
|
["cross-ns methods visible" "[:sql]"
|
||||||
"(do (ns cf.mm) (defmulti ext identity) (defmethod ext :default [_] :d) (defn allk [] (vec (for [[k v] (methods ext) :when (not= k :default)] k))) (ns cf.mmi) (defmethod cf.mm/ext :sql [_] :s) (in-ns (quote user)) (cf.mm/allk))"]
|
"(do (ns cf.mm) (defmulti ext identity) (defmethod ext :default [_] :d) (defn allk [] (vec (for [[k v] (methods ext) :when (not= k :default)] k))) (ns cf.mmi) (defmethod cf.mm/ext :sql [_] :s) (in-ns (quote user)) (cf.mm/allk))"]
|
||||||
|
|
||||||
|
### ---- defmacro surface + syntax-quote/ns (enabling real clojure libs) ----
|
||||||
|
# multi-arity defmacro (clojure.tools.logging/log has 4 arities)
|
||||||
|
["defmacro multi-arity" "[6 5 6]"
|
||||||
|
"(do (defmacro mar ([a] (list (quote +) a 1)) ([a b] (list (quote +) a b)) ([a b c] (list (quote +) a b c))) [(mar 5) (mar 2 3) (mar 1 2 3)])"]
|
||||||
|
# defmacro with docstring AND attr-map before the params (every tools.logging
|
||||||
|
# level macro is shaped this way)
|
||||||
|
["defmacro doc + attr-map" "10"
|
||||||
|
"(do (defmacro mam \"doc\" {:arglists (quote ([x]))} [x] (list (quote inc) x)) (mam 9))"]
|
||||||
|
# syntax-quote resolves a namespace ALIAS to its target ns, so a macro's
|
||||||
|
# template resolves at the USE site (jolt-9av)
|
||||||
|
["syntax-quote resolves alias" "\"HI\""
|
||||||
|
"(do (ns sq.lib (:require [clojure.string :as s])) (defmacro up [x] `(s/upper-case ~x)) (in-ns (quote user)) (sq.lib/up \"hi\"))"]
|
||||||
|
# ^{:map} metadata on an ns name (jolt-8w2): the ns name is the bare symbol
|
||||||
|
["ns name with ^{:map} meta" "5"
|
||||||
|
"(do (ns ^{:author \"a\" :doc \"d\"} nm.meta) (def q 5) (in-ns (quote user)) nm.meta/q)"]
|
||||||
|
# ~*ns* splices the live namespace object into a template (it self-evaluates)
|
||||||
|
["unquote *ns* in template" "true"
|
||||||
|
"(do (defmacro cur-ns [] `(str ~*ns*)) (string? (cur-ns)))"]
|
||||||
])
|
])
|
||||||
|
|
||||||
# Run every case under a given context factory and return the failures. The same
|
# Run every case under a given context factory and return the failures. The same
|
||||||
|
|
|
||||||
|
|
@ -259,8 +259,12 @@
|
||||||
["file-seq finds files" "true"
|
["file-seq finds files" "true"
|
||||||
"(do (require '[clojure.java.io :as io]) (pos? (count (filter (fn [f] (.isFile f)) (file-seq (io/file \"docs\"))))))"])
|
"(do (require '[clojure.java.io :as io]) (pos? (count (filter (fn [f] (.isFile f)) (file-seq (io/file \"docs\"))))))"])
|
||||||
|
|
||||||
# clojure.tools.logging shim (jolt-nzg): the macro surface works, debug/trace
|
# The REAL clojure.tools.logging (vendored verbatim) runs on jolt: a jolt
|
||||||
# are suppressed (no-op nil) by default, and the impl protocol layer is present.
|
# clojure.tools.logging.impl backend (stderr LoggerFactory) plus host shims
|
||||||
|
# (agent/send-off, clojure.lang.LockingTransaction/isRunning, a clojure.pprint
|
||||||
|
# subset, clojure.string/trim-newline) and the defmacro/syntax-quote/ns fixes.
|
||||||
|
# These cases assert the public API behaves; the level rows return nil because
|
||||||
|
# the real level macros return the (nil) result of log* when enabled.
|
||||||
(defspec "host-interop / clojure.tools.logging"
|
(defspec "host-interop / clojure.tools.logging"
|
||||||
["info returns nil" "nil" "(do (require '[clojure.tools.logging :as log]) (log/info \"hi\" 42))"]
|
["info returns nil" "nil" "(do (require '[clojure.tools.logging :as log]) (log/info \"hi\" 42))"]
|
||||||
["debug suppressed" "nil" "(do (require '[clojure.tools.logging :as log]) (log/debug \"x\"))"]
|
["debug suppressed" "nil" "(do (require '[clojure.tools.logging :as log]) (log/debug \"x\"))"]
|
||||||
|
|
@ -269,4 +273,20 @@
|
||||||
["enabled? debug" "false" "(do (require '[clojure.tools.logging :as log]) (log/enabled? :debug))"]
|
["enabled? debug" "false" "(do (require '[clojure.tools.logging :as log]) (log/enabled? :debug))"]
|
||||||
["spy returns value" "3" "(do (require '[clojure.tools.logging :as log]) (log/spy :info (+ 1 2)))"]
|
["spy returns value" "3" "(do (require '[clojure.tools.logging :as log]) (log/spy :info (+ 1 2)))"]
|
||||||
["impl factory name" "\"jolt/stderr\""
|
["impl factory name" "\"jolt/stderr\""
|
||||||
"(do (require '[clojure.tools.logging.impl :as impl]) (impl/name (impl/find-factory)))"])
|
"(do (require '[clojure.tools.logging.impl :as impl]) (impl/name (impl/find-factory)))"]
|
||||||
|
# vars that exist only in the real library (not in a hand-rolled shim)
|
||||||
|
["real lib *tx-agent-levels*" "#{:info :warn}"
|
||||||
|
"(do (require '[clojure.tools.logging :as log]) log/*tx-agent-levels*)"]
|
||||||
|
["real lib log-capture! present" "true"
|
||||||
|
"(do (require '[clojure.tools.logging :as log]) (fn? log/log-capture!))"]
|
||||||
|
["real lib spyf present" "true"
|
||||||
|
"(do (require '[clojure.tools.logging :as log]) (boolean (resolve 'log/spyf)))"])
|
||||||
|
|
||||||
|
# Host shims that let the real clojure.tools.logging load: no STM (a transaction
|
||||||
|
# is never running) and a minimal clojure.pprint (used by spy).
|
||||||
|
(defspec "host-interop / logging host shims"
|
||||||
|
["LockingTransaction/isRunning" "false" "(clojure.lang.LockingTransaction/isRunning)"]
|
||||||
|
["pprint writes value" "\"[1 2 3]\\n\""
|
||||||
|
"(do (require '[clojure.pprint :as pp]) (with-out-str (pp/pprint [1 2 3])))"]
|
||||||
|
["with-pprint-dispatch runs body" "42"
|
||||||
|
"(do (require '[clojure.pprint :as pp]) (pp/with-pprint-dispatch pp/code-dispatch 42))"])
|
||||||
|
|
|
||||||
|
|
@ -93,3 +93,16 @@
|
||||||
["docstring + arity" "15" "(do (defmacro th \"triple\" ([x] (list (quote *) x 3))) (th 5))"]
|
["docstring + arity" "15" "(do (defmacro th \"triple\" ([x] (list (quote *) x 3))) (th 5))"]
|
||||||
["^{:map} name meta" "7" "(do (defmacro ^{:private true} pm [] 7) (pm))"]
|
["^{:map} name meta" "7" "(do (defmacro ^{:private true} pm [] 7) (pm))"]
|
||||||
["multi-form body" "6" "(do (defmacro mb ([a b] (list (quote +) a b))) (mb 2 4))"])
|
["multi-form body" "6" "(do (defmacro mb ([a b] (list (quote +) a b))) (mb 2 4))"])
|
||||||
|
|
||||||
|
# Multi-arity defmacro (dispatch on arg count) and the docstring + attr-map +
|
||||||
|
# params form — both needed to run real Clojure macros, e.g.
|
||||||
|
# clojure.tools.logging/log (4 arities) and its level macros (jolt-q8l, jolt-qnr).
|
||||||
|
(defspec "macros / defmacro multi-arity & attr-map"
|
||||||
|
["multi-arity 1" "6" "(do (defmacro ma ([a] (list (quote +) a 1)) ([a b] (list (quote +) a b))) (ma 5))"]
|
||||||
|
["multi-arity 2" "5" "(do (defmacro ma ([a] (list (quote +) a 1)) ([a b] (list (quote +) a b))) (ma 2 3))"]
|
||||||
|
["arity delegates" "[:d nil 9]"
|
||||||
|
"(do (defmacro lg ([m] `(lg :d nil ~m)) ([l t m] (list (quote vector) l t m))) (lg 9))"]
|
||||||
|
["doc + attr-map + params" "10"
|
||||||
|
"(do (defmacro am \"doc\" {:arglists (quote ([x]))} [x] (list (quote inc) x)) (am 9))"]
|
||||||
|
["doc + attr-map + variadic" "6"
|
||||||
|
"(do (defmacro vg \"d\" {:arglists (quote ([& a]))} [& xs] `(+ ~@xs)) (vg 1 2 3))"])
|
||||||
|
|
|
||||||
|
|
@ -37,3 +37,12 @@
|
||||||
(defspec "state / promises"
|
(defspec "state / promises"
|
||||||
["promise deliver" "5" "(let [p (promise)] (deliver p 5) @p)"]
|
["promise deliver" "5" "(let [p (promise)] (deliver p 5) @p)"]
|
||||||
["promise undelivered" "nil" "(let [p (promise)] @p)"])
|
["promise undelivered" "nil" "(let [p (promise)] @p)"])
|
||||||
|
|
||||||
|
# Minimal synchronous agent shim (jolt has no thread pool/STM) — enough for
|
||||||
|
# libraries that hold an agent without depending on async dispatch.
|
||||||
|
(defspec "state / agents (synchronous shim)"
|
||||||
|
["agent deref" "0" "(deref (agent 0))"]
|
||||||
|
["agent with opts" "0" "(deref (agent 0 :error-mode :continue))"]
|
||||||
|
["send-off applies" "5" "(let [a (agent 0)] (send-off a + 5) (deref a))"]
|
||||||
|
["send applies" "7" "(let [a (agent 1)] (send a + 6) (deref a))"]
|
||||||
|
["agent-error nil" "nil" "(agent-error (agent 0))"])
|
||||||
|
|
|
||||||
|
|
@ -62,3 +62,11 @@
|
||||||
["get out of range nil" "nil" "(get \"abc\" 9)"]
|
["get out of range nil" "nil" "(get \"abc\" 9)"]
|
||||||
["get negative nil" "nil" "(get \"abc\" -1)"]
|
["get negative nil" "nil" "(get \"abc\" -1)"]
|
||||||
["get default honored" ":none" "(get \"abc\" 9 :none)"])
|
["get default honored" ":none" "(get \"abc\" 9 :none)"])
|
||||||
|
|
||||||
|
# clojure.string/trim-newline (ported from clojure.string): strips trailing \n/\r.
|
||||||
|
(defspec "clojure.string / trim-newline"
|
||||||
|
["trailing newline" "\"x\"" "(do (require (quote [clojure.string :as s])) (s/trim-newline \"x\\n\"))"]
|
||||||
|
["trailing \\r\\n" "\"x\"" "(do (require (quote [clojure.string :as s])) (s/trim-newline \"x\\r\\n\"))"]
|
||||||
|
["no trailing" "\"ab\"" "(do (require (quote [clojure.string :as s])) (s/trim-newline \"ab\"))"]
|
||||||
|
["only newlines" "\"\"" "(do (require (quote [clojure.string :as s])) (s/trim-newline \"\\n\\n\"))"]
|
||||||
|
["interior kept" "\"a\\nb\"" "(do (require (quote [clojure.string :as s])) (s/trim-newline \"a\\nb\\n\"))"])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue