Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

275 lines (238 sloc) 8.763 kB
;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and
;; distribution terms for this software are covered by the Eclipse Public
;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;; be found in the file epl-v10.html at the root of this distribution. By
;; using this software in any fashion, you are agreeing to be bound by the
;; terms of this license. You must not remove this notice, or any other,
;; from this software.
;;
;; A repl with that provides support for lines and line numbers in the
;; input stream.
;;
;; scgilardi (gmail)
;; Created 28 November 2008
(ns
#^{:author "Stephen C. Gilardi",
:doc "A repl with that provides support for lines and line numbers in the
input stream."}
clojure.contrib.repl-ln
(:gen-class)
(:import (clojure.lang Compiler LineNumberingPushbackReader RT Var)
(java.io InputStreamReader OutputStreamWriter PrintWriter)
java.util.Date)
(:require clojure.main)
(:use [clojure.contrib.def
:only (defmacro- defonce- defstruct- defvar-)]))
;; Private
(declare repl)
(defstruct- repl-info
:name :started :name-fmt :prompt-fmt :serial :thread :depth)
(defvar- +name-formats+
{"%S" "%1$d" "%T" "%2$d" "%D" "%3$d"}
"For set-name, maps our dynamic value codes to arg positions in
the call to format in repl-name")
(defvar- +prompt-formats+
{"%S" "%1$d" "%T" "%2$d" "%D" "%3$d" "%L" "%4$d" "%N" "%5$s"}
"For set-prompt, maps our dynamic value codes to arg positions in
the call to format in repl-prompt")
(defvar- +info-format+
["Name: %s"
"Started: %s"
"Name-fmt: \"%s\""
"Prompt-fmt: \"%s\""
"Serial: %d"
"Thread: %d"
"Depth: %d"
"Line: %d"])
(defvar- +info-defaults+
(struct-map repl-info
:name-fmt "repl-%S"
:prompt-fmt "%S:%L %N=> "
:depth 0)
"Default/root values for repl info")
(defonce- *serial-number* (atom 0)
"Serial number counter")
(defonce- *info* +info-defaults+
"Public info for this repl")
(defonce- *private* {}
"Private info for this repl")
(defmacro- update
"Replaces the map thread-locally bound to map-var with a copy that
includes updated and/or new values from keys and vals."
[map-var & key-vals]
`(set! ~map-var (assoc ~map-var ~@key-vals)))
(defn- repl-name
"Returns the repl name based on this repl's name-fmt"
[]
(let [{:keys [name-fmt]} *private*
{:keys [serial thread depth]} *info*]
(format name-fmt serial thread depth)))
(defn- prompt-hook
[]
(let [prompt (*private* :prompt)]
(var-set Compiler/LINE (.getLineNumber *in*))
(prompt)))
(defn- process-inits
"Processes initial pairs of args of the form:
-i filepath, or
--init filepath
by loading the referenced files, then accepts an optional terminating arg
of the form:
-r, or
--repl
Returns a seq of any remaining args."
[args]
(loop [[init filename & more :as args] args]
(if (#{"-i" "--init"} init)
(do
(clojure.main/load-script filename)
(recur more))
(if (#{"-r" "--repl"} init)
(rest args)
args))))
(defn- process-command-line
"Args are strings passed in from the command line. Loads any requested
init files and binds *command-line-args* to a seq of the remaining args"
[args]
(set! *command-line-args* (process-inits args)))
(defn stream-repl
"Repl entry point that provides convenient overriding of input, output,
and err streams via sequential keyword-value pairs. Default values
for :in, :out, and :err are streams associated with System/in,
System/out, and System/err using UTF-8 encoding. Also supports all the
options provided by clojure.contrib.repl-ln/repl."
[& options]
(let [enc RT/UTF8
{:keys [in out err]
:or {in (LineNumberingPushbackReader.
(InputStreamReader. System/in enc))
out (OutputStreamWriter. System/out enc)
err (PrintWriter. (OutputStreamWriter. System/err enc))}}
(apply hash-map options)]
(binding [*in* in *out* out *err* err]
(apply repl options))))
(defn- -main
"Main entry point, starts a repl enters the user namespace and processes
command line args."
[& args]
(repl :init
(fn []
(println "Clojure" (clojure-version))
(in-ns 'user)
(process-command-line args))))
;; Public
(defn repl-prompt
"Returns the current repl prompt based on this repl's prompt-fmt"
[]
(let [{:keys [prompt-fmt]} *private*
{:keys [serial thread depth]} *info*
line (.getLineNumber *in*)
namespace (ns-name *ns*)]
(format prompt-fmt serial thread depth line namespace)))
(defn set-repl-name
"Sets the repl name format to the string name-fmt. Include the following
codes in the name to make the corresponding dynamic values part of it:
%S - repl serial number
%T - thread id
%D - nesting depth in this thread
With no arguments, resets the repl name to its default: \"repl-%S\""
([]
(set-repl-name (+info-defaults+ :name-fmt)))
([name-fmt]
(update *info* :name-fmt name-fmt)
(loop [[[code fmt] & more] (seq +name-formats+)
name-fmt name-fmt]
(if code
(recur more (.replace name-fmt code fmt))
(update *private* :name-fmt name-fmt)))
(let [name (repl-name)]
(update *info* :name name)
(var-set Compiler/SOURCE name))
nil))
(defn set-repl-prompt
"Sets the repl prompt. Include the following codes in the prompt to make
the corresponding dynamic values part of it:
%S - repl serial number
%T - thread id
%D - nesting depth in this thread
%L - input line number
%N - namespace name
With no arguments, resets the repl pompt to its default: \"%S:%L %N=> \""
([]
(set-repl-prompt (+info-defaults+ :prompt-fmt)))
([prompt-fmt]
(update *info* :prompt-fmt prompt-fmt)
(loop [[[code fmt] & more] (seq +prompt-formats+)
prompt-fmt prompt-fmt]
(if code
(recur more (.replace prompt-fmt code fmt))
(update *private* :prompt-fmt prompt-fmt)))
nil))
(defn repl-info
"Returns a map of info about the current repl"
[]
(let [line (.getLineNumber *in*)]
(assoc *info* :line line)))
(defn print-repl-info
"Prints info about the current repl"
[]
(let [{:keys [name started name-fmt prompt-fmt serial thread depth line]}
(repl-info)]
(printf
(apply str (interleave +info-format+ (repeat "\n")))
name started name-fmt prompt-fmt serial thread depth line)))
(defn repl
"A repl that supports line numbers. For definitions and evaluations made
at the repl, the repl-name and line number will be reported as the
origin. Use set-repl-name and set-repl-prompt to customize the repl name
and prompt. This repl supports all of the keyword arguments documented
for clojure.main/repl with the following change and additions:
- :prompt has a new default
default: #(clojure.core/print (repl-prompt))
- :name-fmt, Name format string
default: the name-fmt of the parent repl, or \"repl-%S\"
- :prompt-fmt, Prompt format string
default: the prompt-fmt of the parent repl, or \"%S:%L %N=> \""
[& options]
(let [{:keys [init need-prompt prompt flush read eval print caught
name-fmt prompt-fmt]
:or {init #()
need-prompt (if (instance? LineNumberingPushbackReader *in*)
#(.atLineStart *in*)
#(identity true))
prompt #(clojure.core/print (repl-prompt))
flush flush
read clojure.main/repl-read
eval eval
print prn
caught clojure.main/repl-caught
name-fmt (*info* :name-fmt)
prompt-fmt (*info* :prompt-fmt)}}
(apply hash-map options)]
(try
(Var/pushThreadBindings
{Compiler/SOURCE (var-get Compiler/SOURCE)
Compiler/LINE (var-get Compiler/LINE)
(var *info*) *info*
(var *private*) {}})
(update *info*
:started (Date.)
:serial (swap! *serial-number* inc)
:thread (.getId (Thread/currentThread))
:depth (inc (*info* :depth)))
(update *private*
:prompt prompt)
(set-repl-name name-fmt)
(set-repl-prompt prompt-fmt)
(clojure.main/repl
:init init
:need-prompt need-prompt
:prompt prompt-hook
:flush flush
:read read
:eval eval
:print print
:caught caught)
(finally
(Var/popThreadBindings)
(prn)))))
Jump to Line
Something went wrong with that request. Please try again.