├── .gitignore ├── src ├── scratch │ ├── system.clj │ ├── java.clj │ ├── fs.clj │ ├── json.clj │ └── net.clj └── scratch.clj ├── deps.edn ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache/ 2 | .nrepl-port 3 | .rebel_readline_history 4 | -------------------------------------------------------------------------------- /src/scratch/system.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.system) 2 | 3 | (def username (System/getProperty "user.name")) 4 | (def home (System/getProperty "user.home")) 5 | (def pwd (System/getProperty "user.dir")) 6 | (def os {:name (System/getProperty "os.name") 7 | :version (System/getProperty "os.version")}) 8 | -------------------------------------------------------------------------------- /src/scratch/java.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.java 2 | (:require 3 | [clojure.reflect :refer [reflect]])) 4 | 5 | (defn jmethods 6 | "Print methods of a Java object" 7 | [o] 8 | (->> o 9 | reflect 10 | :members 11 | (filter :exception-types) 12 | (sort-by :name) 13 | (map #(select-keys % [:name :parameter-types])))) 14 | -------------------------------------------------------------------------------- /src/scratch/fs.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.fs 2 | (:require 3 | [clojure.java.io :as io] 4 | [scratch.system :refer [pwd]])) 5 | 6 | (defn ls 7 | ([] 8 | (ls pwd)) 9 | ([f] 10 | (map #(hash-map :name (.getName %) :dir (.isDirectory %)) 11 | (.listFiles (io/file f))))) 12 | 13 | (defn dir? 14 | "Check if f is a directory" 15 | [f] 16 | (.isDirectory (io/file f))) 17 | 18 | (defn exists? 19 | "Check if f exists" 20 | [f] 21 | (.exists (io/file f))) 22 | -------------------------------------------------------------------------------- /src/scratch/json.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.json 2 | (:refer-clojure :exclude [str read]) 3 | (:require 4 | [clojure.java.io :as io] 5 | [jsonista.core :as json])) 6 | 7 | (defn str 8 | "Return JSON string of data" 9 | [data] 10 | (json/write-value-as-string data)) 11 | 12 | (defn strp 13 | "Return pretty JSON string of data" 14 | [data] 15 | (json/write-value-as-string data (json/object-mapper {:pretty true}))) 16 | 17 | (defn write 18 | "Write data as JSON to file" 19 | [file data] 20 | (json/write-value (io/file file) data (json/object-mapper {:pretty true}))) 21 | 22 | (defn read 23 | "Read data as JSON from string or file" 24 | [file] 25 | (json/read-value file (json/object-mapper {:decode-key-fn true}))) 26 | 27 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {com.bhauman/rebel-readline {:mvn/version "0.1.4"} 2 | cider/cider-nrepl {:mvn/version "0.21.1"} 3 | com.rpl/specter {:mvn/version "1.1.2"} 4 | aleph {:mvn/version "0.4.6"} 5 | metosin/jsonista {:mvn/version "0.2.2"} 6 | clojure.java-time {:mvn/version "0.3.2"} 7 | org.clojure/java.jdbc {:mvn/version "0.7.8"} 8 | org.xerial/sqlite-jdbc {:mvn/version "3.25.2"} 9 | org.postgresql/postgresql {:mvn/version "42.2.4"} 10 | mysql/mysql-connector-java {:mvn/version "8.0.15"} 11 | buddy/buddy-core {:mvn/version "1.5.0"} 12 | buddy/buddy-hashers {:mvn/version "1.3.0"} 13 | hickory {:mvn/version "0.7.1"} 14 | hiccup {:mvn/version "1.0.5"}}} 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jorin Vogel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/scratch.clj: -------------------------------------------------------------------------------- 1 | (ns scratch 2 | (:require 3 | [clojure.repl :refer [apropos dir doc source]] 4 | [clojure.java.io :as io] 5 | [clojure.java.shell :refer [sh]] 6 | [clojure.data :refer [diff]] 7 | [clojure.edn :as edn] 8 | [clojure.pprint :refer [pp pprint print-table]] 9 | [clojure.set :as set] 10 | [clojure.spec.alpha :as spec] 11 | [clojure.string :as str] 12 | [clojure.walk :refer [postwalk]] 13 | [rebel-readline.core] 14 | [rebel-readline.clojure.main] 15 | [rebel-readline.clojure.line-reader] 16 | [rebel-readline.clojure.service.local] 17 | [cider-nrepl.main :as cider] 18 | [scratch.system :refer [username home pwd os]] 19 | [scratch.java :refer [jmethods]] 20 | [scratch.json :as json] 21 | [scratch.net :refer [valid-port? valid-url? get-free-port hostname ping nslookup]] 22 | [scratch.fs :refer [ls dir? exists?]])) 23 | 24 | (defn -main [] 25 | (in-ns 'scratch) 26 | (doto (Thread. #(cider/init ['cider.nrepl/cider-middleware])) (.setDaemon true) .start) 27 | (rebel-readline.core/with-line-reader 28 | (rebel-readline.clojure.line-reader/create 29 | (rebel-readline.clojure.service.local/create)) 30 | (clojure.main/repl 31 | :prompt (fn []) ;; prompt is handled by line-reader 32 | :read (rebel-readline.clojure.main/create-repl-read) 33 | :print pprint))) 34 | -------------------------------------------------------------------------------- /src/scratch/net.clj: -------------------------------------------------------------------------------- 1 | (ns scratch.net 2 | (:import (java.net URI InetAddress Inet4Address Inet6Address)) 3 | (:require 4 | [clojure.string :as str])) 5 | 6 | (defn valid-port? [p] 7 | (< 0 p 0x10000)) 8 | 9 | (defn valid-url? [s] 10 | (try 11 | (.toURL (java.net.URI. s)) 12 | true 13 | (catch Exception e false))) 14 | 15 | (defn hostname [s] 16 | (.getHost (new URI s))) 17 | 18 | ; Thanks https://gist.github.com/apeckham/78da0a59076a4b91b1f5acf40a96de69 19 | (defn get-free-port 20 | "Let Java find a free port for you." 21 | [] 22 | (let [socket (java.net.ServerSocket. 0)] 23 | (.close socket) 24 | (.getLocalPort socket))) 25 | 26 | ; Thanks to https://github.com/clojure-cookbook/clojure-cookbook/blob/master/05_network-io/5-03_sending-a-ping-request.asciidoc 27 | (defn ping 28 | "Time an .isReachable ping to a given domain" 29 | ([domain] 30 | (ping domain 3000)) 31 | ([domain timeout] 32 | (let [addr (java.net.InetAddress/getByName domain) 33 | start (. System (nanoTime)) 34 | result (.isReachable addr timeout) 35 | total (/ (double (- (. System (nanoTime)) start)) 1000000.0)] 36 | {:time total 37 | :result result}))) 38 | 39 | (defn nslookup 40 | "Returns a map of found IPs for a given domain" 41 | [domain] 42 | (->> (InetAddress/getAllByName domain) 43 | (map #(cond (instance? Inet4Address %) {:ip4 [(.getHostAddress %)]} 44 | (instance? Inet6Address %) {:ip6 [(.getHostAddress %)]})) 45 | (apply merge-with #(vec (concat %1 %2))))) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scratch 2 | 3 | *Clojure REPL with features loaded ready to be productive* 4 | 5 | Being able to explore a problem with programming tools in an interactive fashion can be very productive. 6 | 7 | There have been countless times I had to do some one-off investigating 8 | and a wrote a quick shell one-liner that grew into a monster of ten pipes 9 | with five sub-shells, three `xargs` and quoted quotes within quoted quotes. 10 | 11 | Clojure is an amazing tool for interactive tasks, but just starting up a plain REPL, 12 | it's hard to be productive right away for one-off tasks. 13 | You need to load some additional libraries depending on your context. 14 | 15 | `scratch` is an opinionated toolbox to have everything ready 16 | whenever I realize my shell commands are getting out of hand again. 17 | 18 | Of course loading all these dependencies comes at a cost, 19 | however not being interrupted while working on a problem can be a good trait-off, 20 | especially if you keep the same REPL session running for most of the time anyways. 21 | The most common tools are required by default and the other dependencies can be loaded on demand. 22 | 23 | ## Run It 24 | 25 | Try it out with: 26 | 27 | ```sh 28 | clojure -Sdeps '{:deps {clj-scratch {:git/url "https://github.com/jorinvo/clj-scratch" :sha "c7dfa3e6f496e30d5ff39506fb893c5fc733081c"}}}' -m scratch 29 | ``` 30 | 31 | A Git hash can be used to simply try out the REPL without downloading anything manually, 32 | but I prefer using a local copy of the repository so I can easily adopt changes as I go. 33 | 34 | I created an alias like this: 35 | 36 | ```sh 37 | alias scratch='clojure -Sdeps "{:deps {clj-scratch {:local/root \"/path/to/clj-scratch\"}}}" -m scratch' 38 | ``` 39 | 40 | 41 | ## Features 42 | 43 | ### Dev Tools 44 | 45 | - Use [rebel-readline](https://github.com/bhauman/rebel-readline) with all its nice tools and shortcuts 46 | - Pretty printed REPL output 47 | - [CIDER nREPL](https://github.com/clojure-emacs/cider-nrepl) server to connect from your editor 48 | - View docs with [`(doc map)`](https://clojuredocs.org/clojure.repl/doc) 49 | - View source with [`(source map)`](https://clojuredocs.org/clojure.repl/source) 50 | - List public vars with [`(dir clojure.string)`](https://clojuredocs.org/clojure.repl/dir) 51 | - Search vars with [`(apropos str)`](https://clojuredocs.org/clojure.repl/apropos) 52 | - Quick info about Java methods of an object with `(jmethods "string")` 53 | - Table output with `(print-table (jmethods 0))` 54 | 55 | ### OS 56 | 57 | - Info About Operating System in vars `username`, `home`, `pwd`, `os` 58 | - Run shell commands using `(sh "cowsay" "hi")` 59 | 60 | ### File System 61 | 62 | - Directory listing with `(ls)` or `(ls "some/path")` 63 | - Check file properties with helpers like `(dir? "some/path")` and `(exists? "some/path")` 64 | - Also `clojure.java.io` is available as `io`. Checkout `(jmethods (io/file "."))` 65 | 66 | ### JSON 67 | 68 | - Using [jsonista](https://github.com/metosin/jsonista) 69 | - Read JSON from file or streams with `(json/read (io/file "some.json"))` 70 | - Output data as JSON with `(json/str data)`, `(json/strp data)` or `(json/write "path/out.json" data)` 71 | 72 | ### Networking 73 | 74 | - Check network types with helpers like `(valid-port? 112233)` or `(valid-url? "not-valid")` 75 | - Get host name from URL with `(hostname "https://example.com")` 76 | - Get a random free port with `(get-free-port)` 77 | - Ping a domain with `(ping "example.com")` 78 | - Get IPs for a domain with `(nslookup "example.com")` 79 | 80 | #### [Aleph](https://github.com/ztellman/aleph) 81 | 82 | Aleph provides great tooling to work with HTTP, WebSockets, TCP and UDP as client and server. 83 | 84 | It's included as dependency but not loaded by default: 85 | 86 | ```clojure 87 | (require '[aleph.http :as http] 88 | '[aleph.tcp :as tcp] 89 | '[aleph.udp :as udp] 90 | '[byte-streams :as byte-streams] 91 | '[manifold.stream :as stream] 92 | '[manifold.deferred :as deferred]) 93 | ``` 94 | 95 | Example - Make a HTTP requests: 96 | 97 | ```clojure 98 | (-> @(http/get "https://example.com") :body byte-streams/to-string) 99 | ``` 100 | 101 | Checkout [Aleph](https://github.com/ztellman/aleph) for more. 102 | 103 | ### HTML 104 | 105 | - [hiccup](https://github.com/weavejester/hiccup) to generate HTML 106 | - Parse HTML with [tagsoup](https://github.com/nathell/clj-tagsoup) 107 | - Use [specter](https://github.com/nathanmarz/specter) to transform deeply nested structures (like HTML!) 108 | 109 | The tools are included as dependencies but not loaded by default: 110 | 111 | ```clojure 112 | (require '[hiccup.core :refer [html]]) 113 | (require '[com.rpl.specter :as specter]) 114 | (require '[pl.danieljanus.tagsoup :as tagsoup]) 115 | 116 | (def data (tagsoup/parse "https://example.com")) 117 | 118 | (def CHILDREN (specter/nthpath 2)) 119 | 120 | (defn where-tag [tag] 121 | [(specter/pred #(= tag (first %))) CHILDREN]) 122 | 123 | (def TITLE [CHILDREN 124 | (where-tag :head) 125 | (where-tag :title)]) 126 | 127 | (html (specter/transform TITLE str/upper-case data)) 128 | ``` 129 | 130 | ### SQL 131 | 132 | - [`jdbc`](https://github.com/clojure/java.jdbc) is available 133 | - Make sure to require it first: 134 | 135 | ```clojure 136 | (require '[clojure.java.jdbc :as jdbc]) 137 | ``` 138 | 139 | - SQLite, PostgreSQL and MySQL drivers are included 140 | 141 | ### Working with time 142 | 143 | - Using [java-time](https://github.com/dm3/clojure.java-time) 144 | 145 | ```clojure 146 | (require '[java-time :as time]) 147 | ``` 148 | 149 | - There are excellent examples [here](https://github.com/dm3/clojure.java-time#an-appetizer) 150 | 151 | ### Cryptography 152 | 153 | - [buddy](https://github.com/funcool/buddy-core) is included to provide crypto functionality 154 | 155 | ```clojure 156 | (require '[buddy.core.hash :as hash] 157 | '[buddy.core.mac :as mac] 158 | '[buddy.core.codecs :as codecs] 159 | '[buddy.core.codecs.base64 :as base64] 160 | '[buddy.hashers :as hashers]) 161 | ``` 162 | 163 | - Common namespaces are available: `hash`, `mac`, `codecs`, `base64`` hashers` 164 | - sha hash: `(-> (hash/sha256 "some val") (codecs/bytes->hex))` 165 | - base64 string: `(codecs/bytes->str (base64/encode "some val"))` 166 | 167 | ### More core data structures 168 | 169 | Commonly used core namespaces are already available: 170 | 171 | ```clojure 172 | [clojure.edn :as edn] 173 | [clojure.data :refer [diff]] 174 | [clojure.set :as set] 175 | [clojure.string :as str] 176 | [clojure.walk :refer [postwalk]] 177 | [clojure.spec.alpha :as spec] 178 | ``` 179 | 180 | 181 | ## License 182 | 183 | [MIT](https://github.com/jorinvo/clj-scratch/blob/master/LICENSE) 184 | --------------------------------------------------------------------------------