How to run an interactive CLI program from within Clojure? - clojure

I'd like to run an interactive CLI program from within Clojure (e.g., vim) and be able to interact with it.
In bash and other programming languages, I can do that with
vim > `tty`
I tried to do the same in Clojure:
(require '[clojure.java.shell :as shell])
(shell/sh "vim > `tty`")
but it just opens vim without giving me tty.
Background: I'm developing a Clojure CLI tool which parses emails and lets a user edit the parsed data before saving them on the disk. It works the following way:
Read a file with email content and parse it. Each email is stored as a separate file.
Show a user the parsed data and let the user edit the data in vim. Internally I create a temporary file with the parsed data, but I don't mind doing it another way if that would solve my issue.
After a user finished editing the parsed data (they might decide to keep it as it is) append the data to a file on a disk. So all parsed data are saved to the same file.
Go to 1st step if there are any files with emails left.

This code relies on Clojure Java interop to make use of Java's ProcessBuilder class.
(defn -main
[]
;use doseq instead of for because for is lazily evaluated
(doseq [i [1 2 3]]
;extract current directory from system variable
(let [file-name (str "test" i ".txt")
working-directory (trim-newline (:out (sh "printenv" "PWD")))]
(spit file-name "")
;this is where fun begins. We use ProcessBuilder to forward commands to terminal
;we pass a list of commands and their arguments to its constructor
(let [process-builder (java.lang.ProcessBuilder. (list "vim" (str working-directory "/" file-name)))
;inherit is a configuration constant
inherit (java.lang.ProcessBuilder$Redirect/INHERIT)]
;we configure input, output and error redirection
(.redirectOutput process-builder inherit)
(.redirectError process-builder inherit)
(.redirectInput process-builder inherit)
;waitFor used to block execution until vim is closed
(.waitFor (.start process-builder))
)
;additional processing here
)
)
;not necessary but script tends to hang for around 30 seconds at end of its execution
;so this command is used to terminate it instantly
(System/exit 0)
)

Related

Clojure slurp Connection refused

I've created a project using Luminus with the following command:
lein new luminus myapp +mysql +kee-frame +swagger +oauth +kibit
In this project I created an API call that calls a function to read a file from disk.
The API Call (partly):
{:get {:summary "Returns a file from disk to show in GUI"
:responses {200 {:body ::val/my-file}}
:handler (constantly
{:status 200
:body (gui/get-file)})}}
The function get-file is in the namespace myapp.db.gui-related and looks like this:
(defn get-file
[]
(log/info "Trying to retrieve file")
(read-string (slurp "file.edn")))
When I start a REPL and enter the command (mount/start) the application starts normally and I can make the API call to see the content of the file. The file location is now in the root of my project.
However, if I place the file in resources/myapp and change the slurp command to either (slurp "myapp/file.edn") or (slurp "resources/myapp/file.edn"), I get an error after running (mount/start) and the application is not started.
I get the same error if I try to read a file with an absolute path like (slurp "C:\\Development\\file.edn")
2020-04-30 12:02:03,512 [nREPL-session-88d31cf8-b119-4a87-901d-3eee1eb61cc7] INFO myapp.env --=[myapp started successfully using the development profile]=-
2020-04-30 12:02:03,607 [nREPL-session-88d31cf8-b119-4a87-901d-3eee1eb61cc7] INFO myapp.db.gui-related - Trying to retrieve file
Execution error (ConnectException) at java.net.DualStackPlainSocketImpl/connect0 (DualStackPlainSocketImpl.java:-2).
Connection refused: connect
In this project I'll have a lot of 'config' files to read and I would like to be able to place them in one or more sub folders in the root of my project.
Besides this there will be another file to be read in, which will be outside the folder structure of my application.
How can I read these files?
With java you should always use forward slashes; that is (slurp "C:/Development/file.edn"), I think (I don't have windows).
Also reading from relative path should work; you can check the current working dir for your REPL with (System/getProperty "user.dir") and verify that the path is correct.
Sorry. Error had nothing to do with the slurp function. In another function I still had a URL for an API call hard coded and this had changed. Therefore I got this
Execution error (ConnectException) at java.net.DualStackPlainSocketImpl/connect0 (DualStackPlainSocketImpl.java:-2).
Connection refused: connect
It had nothing to do with reading the file but with trying to reach a localhost:5001 instead of localhost:5000.
Thanks for your support

How can I get readline/rlwrap-like functionality when using clojure.main/repl?

How can I get readline-like (or rlwrap-like) functionality from my REPL when I use the repl function from clojure.main?
The background to this is that I'm utilizing and customizing the break function from The Joy of Clojure, First Edition. I'm using it from inside the lein repl REPL. When my "breakpoint" kicks in, the readline-like functionality of Leiningen's REPL is gone, which is kind of inconvenient. My muscle memory makes me hit ↑ followed quickly by Enter. Before I can stop myself, I've got this in my terminal:
debug=> ^[[A
CompilerException java.lang.RuntimeException: Unable to resolve symbol: in this context, compiling:(/tmp/form-init13211381000659590518.clj:1:1)
And now my REPL is stuck and I have to kill the terminal or the process to get out. I'd like very much if I could either get readline working in this second-level REPL or at least prevent this common issue from derailing my debug sessions.
You should use rebel readline, a new repl for clojure developed by bhauman the same guy who brought is figwheel.
https://github.com/bhauman/rebel-readline
It has rlwrap features, syntax highlighting and multi line code editing all in the terminal.
I'm not sure the rlwrap utility would help there, because the inner REPL is held by the outer one. So the input is being controlled by Java code, not the rlwrap tool.
You are causing an exception since you input a wrong value. I remember, the clojure.main/repl function might take an additional argument to handle exceptions. Probably, you could handle it somehow and just print a string "wrong input value" instead. Take a look at the documentation for REPL.
Also, you may implement your own REPL for debugging. Long ago, I used to write some kind of it, here what I've got:
(defn repl []
(let [input (read)]
(if (= input 'q)
nil
(do
(try
(let [result (eval input)]
(println result))
(catch Exception e
(println e)))
(recur)))))
That function just prompts for a proper Clojure expression in an endless loop, evaluates it and prints the result. In case of a wrong input, it prints the error and carries on. To leave the REPL, input q.
Example:
(repl)
(+ 1 2) ;; 2
fsdf8 9_fsd ;; prints a stack trace
q ;; returns nil and exit
Try Emacs with Cider as your repl. When you (break) you'll be bumped out of the Cider repl and into the Emacs Minibuffer, where your standard emacs editing shortcuts (upon which readline is modeled) continue to apply.

Getting a dump of all the user-created functions defined in a repl session in clojure

Is there a way to get a dump of all the source code I have entered into a repl session. I have created a bunch of functions using (defn ...) but did it 'on the fly' without entering them in a text file (IDE) first.
Is there a convenience way to get the source back out of the repl session?
I note that:
(dir user)
will give me a printed list of type:
user.proxy$java.lang.Object
so I can't appear to get that printed list into a Seq for mapping a function like 'source' over. And even if I could then:
(source my-defined-fn)
returns "source not found"...even though I personally entered it in to the repl session.
Any way of doing this? Thanks.
Sorry, but I suspect the answer is no :-/
The best you get is scrolling up in the repl buffer to where you defined it. The source function works by looking in the var's metadata for the file and line number where the functions code is (or was last time it was evaluated), opening the file, and printing the lines. It looks like this:
...
(when-let [filepath (:file (meta v))]
(when-let [strm (.getResourceAsStream (RT/baseLoader) filepath)]
(with-open [rdr (LineNumberReader. (InputStreamReader. strm))]
(dotimes [_ (dec (:line (meta v)))] (.readLine rdr))
...
Not including the full source in the metadata was done on purpose to save memory in the normal case, though it does make it less convenient here.

Clojure (read-line) doesn't wait for input

I am writing a text game in Clojure. I want the player to type lines at the console, and the game to respond on a line-by-line basis.
Research showed me that (read-line) is the way one is meant to get text lines from standard input in Clojure, but it is not working for me.
I am in a fresh Leiningen project, and I have added a :main clause to the project.clj pointing to the only source file:
(ns textgame.core)
(defn -main [& args]
(println "Entering -main")
; (flush) ;makes no difference if flush are commented out
(let [input (read-line)]
(println "ECHO:" input))
; (flush)
(println "Exiting -main"))
using lein run yields:
Entering -main
ECHO: nil
Exiting -main
In other words, there is no opportunity to enter text at the console for (read-line) to read.
How should I get Clojure to wait for characters and newline to be entered and return the corresponding string?
(I am using GNOME Terminal 2.32.1 on Linux Mint 11, Leiningen 1.6.1.1 on Java 1.6.0_26 Java HotSpot(TM) 64-Bit Server VM, Clojure version 1.2.1.)
Update: If I run lein repl, I can (println (read-line)), but not when I have a -main function and run using lein run.
Try "lein trampoline run". See http://groups.google.com/group/leiningen/browse_thread/thread/a07a7f10edb77c9b for more details also from https://github.com/technomancy/leiningen:
Q: I don't have access to stdin inside my project.
A: There's a problem in the library that Leiningen uses to spawn new processes that blocks access to console input. This means that functions like read-line will not work as expected in most contexts, though the repl task necessarily includes a workaround. You can also use the trampoline task to launch your project's JVM after Leiningen's has exited rather than launching it as a subprocess.
I have had similar problems and resorted to building a jar file and then running that.
lein uberjar
java -jar project-standalone.jar
It's a bit slower, though it got me unstuck. An answer that works from the repl would
be better
Wrap your read-line calls with the macro with-read-line-support which is now in ns swank.core [since swank-clojure 1.4+ I believe]:
(use 'swank.core)
(with-read-line-support
(println "a line from Emacs:" (read-line)))
Thanks to Tavis Judd for the fix.
You can use read and use a string as input.
Not sure about the lein aspects of the problem, but definitely in emacs it is impossible to make stdin work. However, if you want to get text from the user, you can easily do it using a JOptionPane like this code from my little tic-tac-toe program:
(defn get-input []
(let [input (JOptionPane/showInputDialog "Enter your next move (row/column)")]
(map #(Integer/valueOf %) (.split input "/"))))

Clojure: Converting Clojure File to YAML

How would you convert a clojure source file to YAML? I have used the clj-yaml library to do it in the interactive REPL, but I'd like to automate this, so I can pass in an input file and specify an output, ie:
clj2yaml input.clj > output.yml
As I understand it you need help to read and write the files?! See slurp and spit. For a real example of reading a YAML config file and parsing it with clj-yaml, see pswincom.gateway.config.
And here's an implementation of a simple clojure tool to do the convertion:
(ns sample
(:require [clj-yaml.core :as yaml]))
(->> (slurp (nth *command-line-args* 0))
read-string ; converts the file content to a clojure datastructure
yaml/generate-string
(spit (nth *command-line-args* 1)))
(On Windows) I can create a batch file called clj2yaml.bat to make it easy to use. It assumes the needed jar-files are located in the current directory. I'm just a novice when it comes to this kind of execution, so a better script is quite likely possible, but here it is:
java.exe -cp .\clojure-1.2.0.jar;.\clojure-contrib-1.2.0.jar;.\clj-yaml-0.3.0-20101010.033133-1.jar;.\snakeyaml-1.5.jar clojure.main sample.clj %*
I can now execute clj2yaml foo.clj foo.yaml to create the yaml file.
You already know how to code a clojure converter, you now just need to package it as a standalone application, and possibly create a sh script that just invokes your class.
As an alternative, here's a neat way to do it, if you're on a *nix environment:
#^:shebang '[
exec java -cp "$HOME/src/clj/clojure/clojure.jar" clojure.lang.Script "$0" -- "$#"
]
(your code here)