├── int-test ├── cache │ └── dummy ├── src │ ├── malformed │ │ └── core.cljs │ ├── analysis │ │ └── core.cljs │ ├── no-ns │ │ ├── bar.cljs │ │ └── foo.cljs │ ├── load-me │ ├── run_file │ │ └── core.cljs │ ├── test_main │ │ ├── one.cljs │ │ ├── zero.cljs │ │ ├── unhandled.cljs │ │ ├── args.cljs │ │ ├── exit.cljs │ │ └── core.cljs │ ├── test_require │ │ ├── core.cljs │ │ ├── exit.cljs │ │ └── throw_it.cljs │ ├── test_load_file │ │ ├── core.cljs │ │ ├── error_in_file.cljs │ │ └── exit_in_file.cljs │ ├── test_src_paths │ │ └── core.cljs │ ├── test_main_cli_fn │ │ ├── one.cljs │ │ ├── zero.cljs │ │ ├── core.cljs │ │ ├── unhandled.cljs │ │ └── exit.cljs │ ├── test_tty │ │ ├── stderr.cljs │ │ ├── stdin.cljs │ │ ├── stdout.cljs │ │ ├── stdio.cljs │ │ └── rebinding_err_out.cljs │ ├── test_cache_spec │ │ ├── bar.cljs │ │ └── foo.cljs │ ├── test_exit │ │ ├── core.cljs │ │ └── exit_in_file.cljs │ ├── test_require_macros │ │ └── core.cljc │ ├── test_doc_source │ │ └── core.cljs │ └── test_args │ │ └── args_in_file.cljs ├── src2 │ └── test_src_paths │ │ └── core.cljs ├── test-jar.jar ├── src3 │ ├── calculator.js │ └── deps.cljs ├── script │ ├── certify │ ├── setup-env │ ├── run-tests │ └── int-tests └── expected │ └── PLANCK-ERR.txt ├── planck-c ├── legal.h ├── repl.h ├── keymap.h ├── bundle.h ├── tasks.h ├── timers.h ├── clock.h ├── .gitignore ├── str.h ├── theme.h ├── http.h ├── shell.h ├── io.h ├── archive.h ├── bundle.c ├── jsc_utils.h ├── file.h ├── str.c ├── bundle_inflate.h ├── sockets.h ├── globals.h ├── clock.c ├── tasks.c ├── engine.h ├── timers.c ├── edn.h ├── archive.c ├── theme.c ├── file.c ├── CMakeLists.txt ├── linenoise.h ├── keymap.c ├── jsc_utils.c └── io.c ├── planck-cljs ├── test │ ├── co2.edn │ ├── co0.edn │ ├── co1.edn │ ├── data_readers.cljc │ ├── deps.cljs │ ├── planck │ │ ├── requiring_resolve_ns.cljs │ │ ├── environ_test.cljs │ │ ├── test_runner.cljs │ │ ├── socket_test.cljs │ │ ├── closure_test.cljs │ │ ├── js_deps_test.cljs │ │ ├── shell_test.cljs │ │ └── repl_test.cljs │ ├── foo │ │ ├── data_readers.cljs │ │ ├── closure_defines.cljs │ │ └── core.cljs │ ├── libs │ │ ├── mylib.js │ │ └── other.js │ ├── compile-opts.edn │ └── general │ │ ├── data_readers_test.cljs │ │ ├── cljsjs_libs_test.cljs │ │ ├── closure_defines_test.cljs │ │ ├── closure_libs_test.cljs │ │ ├── fipp_test.cljs │ │ ├── transit_test.cljs │ │ └── core_test.cljs ├── dev │ └── user.clj ├── script │ ├── clean-bundle │ ├── clean │ ├── decode.cljs │ ├── build │ └── build.clj ├── .gitignore ├── project.clj ├── src │ └── planck │ │ ├── shell.clj │ │ ├── bundle.cljs │ │ ├── environ.cljs │ │ ├── from │ │ ├── io │ │ │ └── aviso │ │ │ │ ├── ansi.cljs │ │ │ │ └── ansi.clj │ │ ├── cljs │ │ │ └── core.cljs │ │ └── cljs_bean │ │ │ └── from │ │ │ └── cljs │ │ │ └── core.cljs │ │ ├── core.clj │ │ ├── themes.cljs │ │ ├── repl.clj │ │ ├── console.cljs │ │ ├── closure.cljs │ │ ├── socket.cljs │ │ ├── socket │ │ └── alpha.cljs │ │ ├── js_deps.cljs │ │ ├── pprint │ │ └── width_adjust.cljs │ │ └── repl_resources.cljs └── deps.edn ├── site ├── script │ ├── clean │ ├── project.clj │ ├── cache │ │ └── .gitignore │ ├── deploy │ └── build └── src │ ├── img │ └── screenshot.png │ ├── postamble.html │ ├── preamble.html │ ├── index.md │ └── css │ └── highlightjs.css ├── doc ├── img │ ├── intro.png │ ├── legal.jpg │ ├── repl.jpg │ ├── setup.jpg │ ├── running.jpg │ ├── scripts.jpg │ ├── internals.jpg │ ├── one-liners.jpg │ ├── source-dev.jpg │ ├── contributing.jpg │ ├── dependencies.jpg │ ├── performance.jpg │ ├── socket-repl.jpg │ └── planck-namespaces.jpg ├── legal.md ├── contributing.md ├── gcl.md ├── guide.md ├── one-liners.md ├── intro.md ├── cljdoc.edn ├── setup.md ├── socket-repl.md ├── ides.md ├── testing.md ├── planck-namespaces.md ├── running.md ├── source-dev.md ├── internals.md └── scripts.md ├── script ├── test-int ├── certify ├── build-sandbox ├── test ├── get-cljsjs-long ├── get-tcheck ├── get-closure-compiler ├── test-unit ├── get-build-cache ├── clean ├── install ├── test-core └── get-closure-library ├── .gitignore ├── project.clj ├── .github └── workflows │ └── build_test_planck.yml ├── planck-sh └── plk └── README.md /int-test/cache/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /planck-c/legal.h: -------------------------------------------------------------------------------- 1 | void legal(); -------------------------------------------------------------------------------- /int-test/src/malformed/core.cljs: -------------------------------------------------------------------------------- 1 | ] 2 | -------------------------------------------------------------------------------- /planck-c/repl.h: -------------------------------------------------------------------------------- 1 | int run_repl(); 2 | -------------------------------------------------------------------------------- /int-test/src/analysis/core.cljs: -------------------------------------------------------------------------------- 1 | (defn) 2 | -------------------------------------------------------------------------------- /int-test/src/no-ns/bar.cljs: -------------------------------------------------------------------------------- 1 | (println "bar") 2 | -------------------------------------------------------------------------------- /int-test/src/no-ns/foo.cljs: -------------------------------------------------------------------------------- 1 | (println "foo") 2 | -------------------------------------------------------------------------------- /planck-c/keymap.h: -------------------------------------------------------------------------------- 1 | int load_keymap(char *home); -------------------------------------------------------------------------------- /planck-c/bundle.h: -------------------------------------------------------------------------------- 1 | char *bundle_get_contents(char *path); -------------------------------------------------------------------------------- /planck-cljs/test/co2.edn: -------------------------------------------------------------------------------- 1 | {:test1 :t 2 | :test5 :h 3 | :b 17} 4 | -------------------------------------------------------------------------------- /site/script/clean: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -rf target 4 | -------------------------------------------------------------------------------- /planck-cljs/test/co0.edn: -------------------------------------------------------------------------------- 1 | {:test1 0 2 | :test2 :x 3 | :test3 :y} 4 | -------------------------------------------------------------------------------- /planck-cljs/test/co1.edn: -------------------------------------------------------------------------------- 1 | {:test2 :z 2 | :test4 :j 3 | :test5 :q} 4 | -------------------------------------------------------------------------------- /planck-cljs/test/data_readers.cljc: -------------------------------------------------------------------------------- 1 | {foo/bar foo.data-readers/bar} 2 | -------------------------------------------------------------------------------- /planck-cljs/test/deps.cljs: -------------------------------------------------------------------------------- 1 | {:libs ["planck-cljs/test/libs/mylib.js"]} 2 | -------------------------------------------------------------------------------- /int-test/src/load-me: -------------------------------------------------------------------------------- 1 | (ns load-me.core) 2 | 3 | (def a 3) 4 | (def b 4) 5 | -------------------------------------------------------------------------------- /int-test/src/run_file/core.cljs: -------------------------------------------------------------------------------- 1 | (ns run-file.core) 2 | 3 | (prn ::hello) 4 | -------------------------------------------------------------------------------- /doc/img/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/intro.png -------------------------------------------------------------------------------- /doc/img/legal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/legal.jpg -------------------------------------------------------------------------------- /doc/img/repl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/repl.jpg -------------------------------------------------------------------------------- /doc/img/setup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/setup.jpg -------------------------------------------------------------------------------- /int-test/src/test_main/one.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.one) 2 | 3 | (defn -main [] 4 | 1) 5 | -------------------------------------------------------------------------------- /doc/img/running.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/running.jpg -------------------------------------------------------------------------------- /doc/img/scripts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/scripts.jpg -------------------------------------------------------------------------------- /int-test/src/test_main/zero.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.zero) 2 | 3 | (defn -main [] 4 | 0) 5 | -------------------------------------------------------------------------------- /int-test/src/test_require/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-require.core) 2 | 3 | (def success true) 4 | -------------------------------------------------------------------------------- /doc/img/internals.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/internals.jpg -------------------------------------------------------------------------------- /doc/img/one-liners.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/one-liners.jpg -------------------------------------------------------------------------------- /doc/img/source-dev.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/source-dev.jpg -------------------------------------------------------------------------------- /int-test/src/test_load_file/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-load-file.core) 2 | 3 | (def success true) 4 | -------------------------------------------------------------------------------- /int-test/src/test_src_paths/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-src-paths.core) 2 | 3 | (def src-path :src) 4 | -------------------------------------------------------------------------------- /int-test/src2/test_src_paths/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-src-paths.core) 2 | 3 | (def src-path :src2) 4 | -------------------------------------------------------------------------------- /int-test/test-jar.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/int-test/test-jar.jar -------------------------------------------------------------------------------- /doc/img/contributing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/contributing.jpg -------------------------------------------------------------------------------- /doc/img/dependencies.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/dependencies.jpg -------------------------------------------------------------------------------- /doc/img/performance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/performance.jpg -------------------------------------------------------------------------------- /doc/img/socket-repl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/socket-repl.jpg -------------------------------------------------------------------------------- /site/script/project.clj: -------------------------------------------------------------------------------- 1 | (defproject foo "0.1.0" 2 | :dependencies [[markdown-clj "0.9.85"]]) 3 | -------------------------------------------------------------------------------- /planck-cljs/test/planck/requiring_resolve_ns.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.requiring-resolve-ns) 2 | 3 | (def a 3) 4 | -------------------------------------------------------------------------------- /site/src/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/site/src/img/screenshot.png -------------------------------------------------------------------------------- /doc/img/planck-namespaces.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/planck-repl/planck/HEAD/doc/img/planck-namespaces.jpg -------------------------------------------------------------------------------- /planck-cljs/test/foo/data_readers.cljs: -------------------------------------------------------------------------------- 1 | (ns foo.data-readers) 2 | 3 | (defn bar [x] 4 | (into [] (reverse x))) 5 | -------------------------------------------------------------------------------- /script/test-int: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Running integration tests..." 4 | int-test/script/run-tests 5 | -------------------------------------------------------------------------------- /site/script/cache/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /int-test/src3/calculator.js: -------------------------------------------------------------------------------- 1 | global.Calculator = { 2 | add: function(a, b) { 3 | return a + b; 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /planck-c/tasks.h: -------------------------------------------------------------------------------- 1 | int block_until_tasks_complete(); 2 | 3 | int signal_task_started(); 4 | 5 | int signal_task_complete(); 6 | -------------------------------------------------------------------------------- /int-test/src/test_load_file/error_in_file.cljs: -------------------------------------------------------------------------------- 1 | (ns test-load-file.error-in-file) 2 | 3 | (invalid) 4 | 5 | (def success true) 6 | -------------------------------------------------------------------------------- /planck-cljs/test/foo/closure_defines.cljs: -------------------------------------------------------------------------------- 1 | (ns foo.closure-defines) 2 | 3 | (goog-define bar "x") 4 | 5 | (goog-define baz "y") 6 | -------------------------------------------------------------------------------- /int-test/src/test_main_cli_fn/one.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.one) 2 | 3 | (defn my-main [] 4 | 1) 5 | 6 | (set! *main-cli-fn* my-main) 7 | -------------------------------------------------------------------------------- /int-test/src/test_main_cli_fn/zero.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.zero) 2 | 3 | (defn my-main [] 4 | 0) 5 | 6 | (set! *main-cli-fn* my-main) 7 | -------------------------------------------------------------------------------- /int-test/src/test_tty/stderr.cljs: -------------------------------------------------------------------------------- 1 | (require 'planck.core) 2 | (require 'planck.io) 3 | (pr ['*err* (planck.io/tty? planck.core/*err*)]) 4 | -------------------------------------------------------------------------------- /int-test/src/test_tty/stdin.cljs: -------------------------------------------------------------------------------- 1 | (require 'planck.core) 2 | (require 'planck.io) 3 | (pr ['*in* (planck.io/tty? planck.core/*in*)]) 4 | -------------------------------------------------------------------------------- /int-test/src/test_tty/stdout.cljs: -------------------------------------------------------------------------------- 1 | (require 'planck.core) 2 | (require 'planck.io) 3 | (pr ['*out* (planck.io/tty? cljs.core/*out*)]) 4 | -------------------------------------------------------------------------------- /planck-cljs/test/libs/mylib.js: -------------------------------------------------------------------------------- 1 | goog.provide('my_lib.core') 2 | 3 | my_lib.core = { 4 | add: function(x, y) { return x + y; } 5 | } 6 | -------------------------------------------------------------------------------- /planck-cljs/dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [tubular.core])) 4 | 5 | (defn connect [] 6 | (tubular.core/connect 51638)) 7 | -------------------------------------------------------------------------------- /script/certify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env planck 2 | (ns planck.certify 3 | (:require [planck.shell :refer [sh]])) 4 | (sh "int-test/script/certify") 5 | -------------------------------------------------------------------------------- /int-test/src/test_cache_spec/bar.cljs: -------------------------------------------------------------------------------- 1 | (ns test-cache-spec.bar 2 | (:require 3 | [clojure.spec.alpha :as s])) 4 | 5 | (s/def ::int int?) 6 | -------------------------------------------------------------------------------- /int-test/src/test_require/exit.cljs: -------------------------------------------------------------------------------- 1 | (ns test-require.exit 2 | (:require planck.core)) 3 | 4 | (planck.core/exit 451) 5 | 6 | (def success true) 7 | -------------------------------------------------------------------------------- /planck-c/timers.h: -------------------------------------------------------------------------------- 1 | typedef void (*timer_callback_t)(void *data); 2 | 3 | int start_timer(long millis, timer_callback_t timer_callback, void *data); 4 | -------------------------------------------------------------------------------- /planck-cljs/test/libs/other.js: -------------------------------------------------------------------------------- 1 | goog.provide('other_lib.core') 2 | 3 | other_lib.core = { 4 | subtract: function(x, y) { return x - y; } 5 | } 6 | -------------------------------------------------------------------------------- /int-test/src/test_main_cli_fn/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main-cli-fn.core) 2 | 3 | (defn my-main 4 | [a b] 5 | (prn a b)) 6 | 7 | (set! *main-cli-fn* my-main) 8 | -------------------------------------------------------------------------------- /int-test/src/test_exit/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-exit.core 2 | (:require planck.core)) 3 | 4 | (defn please-exit [exit-value] 5 | (planck.core/exit exit-value)) 6 | -------------------------------------------------------------------------------- /planck-c/clock.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | uint64_t system_time(); 4 | 5 | void init_launch_timing(); 6 | 7 | void display_launch_timing(const char *label); -------------------------------------------------------------------------------- /int-test/src/test_load_file/exit_in_file.cljs: -------------------------------------------------------------------------------- 1 | (ns test-load-file.exit-in-file 2 | (:require planck.core)) 3 | 4 | (planck.core/exit 132) 5 | 6 | (def success true) 7 | -------------------------------------------------------------------------------- /int-test/src/test_require_macros/core.cljc: -------------------------------------------------------------------------------- 1 | (ns test-require-macros.core) 2 | 3 | (defmacro str->int [s] 4 | #?(:clj (Integer/parseInt s) 5 | :cljs (js/parseInt s))) 6 | -------------------------------------------------------------------------------- /int-test/src3/deps.cljs: -------------------------------------------------------------------------------- 1 | {:foreign-libs [{:file "calculator.js" 2 | :provides ["calculator"] 3 | :global-exports {calculator Calculator}}]} 4 | -------------------------------------------------------------------------------- /int-test/src/test_require/throw_it.cljs: -------------------------------------------------------------------------------- 1 | (ns test-require.throw-it) 2 | 3 | (println "before throw") 4 | (throw (js/Error. "bye-bye")) 5 | (println "not here") 6 | (def success true) 7 | -------------------------------------------------------------------------------- /int-test/src/test_exit/exit_in_file.cljs: -------------------------------------------------------------------------------- 1 | (ns test-exit.exit-in-file 2 | (:require planck.core)) 3 | 4 | (planck.core/exit 111) 5 | (println "don't print this") 6 | (def success true) 7 | -------------------------------------------------------------------------------- /planck-c/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /bundle-test 3 | /http-test 4 | /zip-test 5 | /*.o 6 | /*.tar.gz 7 | 8 | /jsc-funcs 9 | 10 | /.planck_cache 11 | /out 12 | 13 | /*.js 14 | /*.jar 15 | -------------------------------------------------------------------------------- /planck-cljs/script/clean-bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | rm -f bundle_dict.c 4 | git update-index --no-assume-unchanged ../planck-c/bundle.c 5 | git checkout -- ../planck-c/bundle.c 6 | -------------------------------------------------------------------------------- /script/build-sandbox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export VERBOSE_BUILD=1 3 | export SANDBOX_BUILD=1 4 | export CLJ_CONFIG=clj-config 5 | script/build -Sdeps "{:mvn/local-repo \"sandbox-m2\"}" 6 | -------------------------------------------------------------------------------- /planck-cljs/test/compile-opts.edn: -------------------------------------------------------------------------------- 1 | {:closure-defines {foo.closure-defines/bar "symbol" 2 | "foo.closure_defines.baz" "string"} 3 | :libs ["planck-cljs/test/libs/other.js"]} 4 | -------------------------------------------------------------------------------- /planck-c/str.h: -------------------------------------------------------------------------------- 1 | int str_has_suffix(const char *str, const char *suffix); 2 | 3 | int str_has_prefix(const char *str, const char *prefix); 4 | 5 | char *str_concat(const char *s1, const char *s2); 6 | -------------------------------------------------------------------------------- /planck-c/theme.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | const char *default_theme_for_terminal(); 4 | 5 | const char *prompt_ansi_code_for_theme(const char *theme); 6 | 7 | bool check_theme(const char *theme); -------------------------------------------------------------------------------- /int-test/src/test_cache_spec/foo.cljs: -------------------------------------------------------------------------------- 1 | (ns test-cache-spec.foo 2 | (:require 3 | [test-cache-spec.bar :as bar] 4 | [clojure.spec.alpha :as s])) 5 | 6 | (defn valid? [x] 7 | (s/valid? ::bar/int x)) 8 | -------------------------------------------------------------------------------- /planck-cljs/.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | /target 3 | /classes 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .hgignore 12 | .hg/ 13 | planck.iml 14 | .idea 15 | -------------------------------------------------------------------------------- /int-test/script/certify: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source int-test/script/setup-env 4 | cp $ACTUAL_PATH/PLANCK-OUT.txt $EXPECTED_PATH/PLANCK-OUT.txt 5 | cp $ACTUAL_PATH/PLANCK-ERR.txt $EXPECTED_PATH/PLANCK-ERR.txt 6 | -------------------------------------------------------------------------------- /int-test/script/setup-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | export EXPECTED_PATH=int-test/expected 4 | export ACTUAL_PATH=/tmp 5 | export PLANCK_BINARY=planck-c/build/planck 6 | export HOME=int-test 7 | export SRC=$HOME/src 8 | -------------------------------------------------------------------------------- /int-test/src/test_main/unhandled.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.unhandled) 2 | 3 | (defn call-two [] 4 | (throw (js/Error. "bye"))) 5 | 6 | (defn call-one [] 7 | (call-two)) 8 | 9 | (defn -main [] 10 | (call-one)) 11 | -------------------------------------------------------------------------------- /int-test/src/test_main/args.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.args 2 | [:require [planck.core :as p]]) 3 | 4 | (defn -main [& args] 5 | (println "args from main:" args) 6 | (println "args from *command-line-args*:" p/*command-line-args*) 7 | 0) 8 | -------------------------------------------------------------------------------- /int-test/src/test_main/exit.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.exit 2 | (:require planck.core)) 3 | 4 | (defn call-two [] 5 | (planck.core/exit 17)) 6 | 7 | (defn call-one [] 8 | (call-two)) 9 | 10 | (defn -main [] 11 | (call-one)) 12 | -------------------------------------------------------------------------------- /int-test/src/test_tty/stdio.cljs: -------------------------------------------------------------------------------- 1 | (require 'planck.core) 2 | (require 'planck.io) 3 | (pr [['*in* (planck.io/tty? planck.core/*in*)] 4 | ['*out* (planck.io/tty? cljs.core/*out*)] 5 | ['*err* (planck.io/tty? planck.core/*err*)]]) 6 | -------------------------------------------------------------------------------- /planck-cljs/project.clj: -------------------------------------------------------------------------------- 1 | (defproject planck "0.0.0" 2 | :plugins [[lein-tools-deps "0.4.1"]] 3 | :middleware [lein-tools-deps.plugin/resolve-dependencies-with-deps-edn] 4 | :lein-tools-deps/config {:config-files [:install :user :project]}) 5 | -------------------------------------------------------------------------------- /planck-cljs/test/general/data_readers_test.cljs: -------------------------------------------------------------------------------- 1 | (ns general.data-readers-test 2 | (:require 3 | [cljs.test :refer-macros [deftest is]] 4 | [foo.data-readers])) 5 | 6 | (deftest data-reader-test 7 | (is (= [3 2 1] #foo/bar [1 2 3]))) 8 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | script/get-tcheck 6 | script/get-cljsjs-long 7 | 8 | echo "Running unit tests..." 9 | script/test-unit 10 | 11 | echo 12 | script/test-int 13 | 14 | echo "All tests have passed." 15 | -------------------------------------------------------------------------------- /int-test/src/test_doc_source/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-doc-source.core 2 | "This is the namespace 3 | docstring for test-doc-source.core") 4 | 5 | (defn my-function 6 | "This is a cool 7 | function with a docstring." 8 | [x] 9 | (* x x)) 10 | -------------------------------------------------------------------------------- /int-test/src/test_tty/rebinding_err_out.cljs: -------------------------------------------------------------------------------- 1 | (require 'planck.core) 2 | (require 'planck.io) 3 | (binding [planck.core/*err* cljs.core/*out*] 4 | (pr [['*out* (planck.io/tty? cljs.core/*out*)] 5 | ['*err* (planck.io/tty? planck.core/*err*)]])) 6 | -------------------------------------------------------------------------------- /planck-c/http.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | JSValueRef function_http_request(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, 4 | size_t argc, const JSValueRef args[], JSValueRef *exception); -------------------------------------------------------------------------------- /planck-c/shell.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | JSValueRef function_shellexec(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, 4 | size_t argc, const JSValueRef args[], JSValueRef *exception); 5 | -------------------------------------------------------------------------------- /int-test/src/test_main_cli_fn/unhandled.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.unhandled) 2 | 3 | (defn call-two [] 4 | (throw (js/Error. "bye"))) 5 | 6 | (defn call-one [] 7 | (call-two)) 8 | 9 | (defn my-main [] 10 | (call-one)) 11 | 12 | (set! *main-cli-fn* my-main) 13 | -------------------------------------------------------------------------------- /planck-cljs/test/general/cljsjs_libs_test.cljs: -------------------------------------------------------------------------------- 1 | (ns general.cljsjs-libs-test 2 | (:require 3 | [cljs.test :refer-macros [deftest is]] 4 | [cljsjs.long])) 5 | 6 | (deftest cljsjs-long-loaded 7 | (is (= "9223372036854775807" (str (js/Long. 0xFFFFFFFF 0x7FFFFFFF))))) 8 | -------------------------------------------------------------------------------- /script/get-cljsjs-long: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 4 | set -x 5 | fi 6 | 7 | mkdir -p lib 8 | if [ ! -f lib/long-3.0.3-1.jar ]; then 9 | cp ~/.m2/repository/cljsjs/long/3.0.3-1/long-3.0.3-1.jar lib/long-3.0.3-1.jar 10 | fi 11 | -------------------------------------------------------------------------------- /int-test/src/test_main_cli_fn/exit.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.exit 2 | (:require planck.core)) 3 | 4 | (defn call-two [] 5 | (planck.core/exit 17)) 6 | 7 | (defn call-one [] 8 | (call-two)) 9 | 10 | (defn my-main [] 11 | (call-one)) 12 | 13 | (set! *main-cli-fn* my-main) 14 | -------------------------------------------------------------------------------- /int-test/src/test_args/args_in_file.cljs: -------------------------------------------------------------------------------- 1 | (ns test-args.args-in-file 2 | (:require planck.core)) 3 | 4 | (println planck.core/*command-line-args*) 5 | (if (exists? *command-line-args*) 6 | (println ^:cljs.analyzer/no-resolve *command-line-args*) 7 | (println planck.core/*command-line-args*)) 8 | -------------------------------------------------------------------------------- /script/get-tcheck: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 4 | set -x 5 | fi 6 | 7 | mkdir -p lib 8 | if [ ! -f lib/test.check-1.1.1.jar ]; then 9 | cp ~/.m2/repository/org/clojure/test.check/1.1.1/test.check-1.1.1.jar lib/test.check-1.1.1.jar 10 | fi 11 | -------------------------------------------------------------------------------- /int-test/src/test_main/core.cljs: -------------------------------------------------------------------------------- 1 | (ns test-main.core) 2 | 3 | (defn square [x] 4 | (* x x)) 5 | 6 | (defn dist [x y] 7 | (Math/sqrt 8 | (+ (square x) 9 | (square y)))) 10 | 11 | (defn -main [a b] 12 | (println 13 | (dist (js/parseInt a) 14 | (js/parseInt b)))) 15 | -------------------------------------------------------------------------------- /planck-cljs/test/foo/core.cljs: -------------------------------------------------------------------------------- 1 | (ns foo.core 2 | (:refer-clojure :exclude [map]) 3 | (:require 4 | [clojure.string :as string] 5 | [clojure.set :as set :refer [union intersection]])) 6 | 7 | ;; A test namespace for testing interns, ns-resolve, ns-aliases, ns-refers 8 | 9 | (def h 3) 10 | -------------------------------------------------------------------------------- /planck-cljs/test/general/closure_defines_test.cljs: -------------------------------------------------------------------------------- 1 | (ns general.closure-defines-test 2 | (:require 3 | [cljs.test :refer-macros [deftest is]] 4 | [foo.closure-defines])) 5 | 6 | (deftest closure-defines-test 7 | (is (= "symbol" foo.closure-defines/bar)) 8 | (is (= "string" foo.closure-defines/baz))) 9 | -------------------------------------------------------------------------------- /int-test/script/run-tests: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source int-test/script/setup-env 4 | int-test/script/gen-actual > $ACTUAL_PATH/PLANCK-OUT.txt 2> $ACTUAL_PATH/PLANCK-ERR.txt 5 | 6 | diff $EXPECTED_PATH/PLANCK-OUT.txt $ACTUAL_PATH/PLANCK-OUT.txt && diff $EXPECTED_PATH/PLANCK-ERR.txt $ACTUAL_PATH/PLANCK-ERR.txt 7 | -------------------------------------------------------------------------------- /planck-c/io.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | char *read_all(FILE *f); 4 | 5 | char *get_contents(char *path, time_t *last_modified); 6 | 7 | void write_contents(char *path, char *contents); 8 | 9 | int mkdir_p(char *path); 10 | 11 | int mkdir_parents(const char *path); 12 | 13 | int copy_file(const char *from, const char *to); -------------------------------------------------------------------------------- /planck-cljs/test/general/closure_libs_test.cljs: -------------------------------------------------------------------------------- 1 | (ns general.closure-libs-test 2 | (:require 3 | [cljs.test :refer-macros [deftest is]] 4 | [my-lib.core] 5 | [other-lib.core])) 6 | 7 | (deftest my-closure-lib-loaded 8 | (is (= 5 (my-lib.core/add 2 3)))) 9 | 10 | (deftest other-closure-lib-loaded 11 | (is (= 3 (other-lib.core/subtract 5 2)))) 12 | -------------------------------------------------------------------------------- /planck-c/archive.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct contents_zip { 4 | uint8_t * payload; 5 | size_t length; 6 | } contents_zip_t; 7 | 8 | void* open_archive(const char *path, char **error_msg); 9 | void close_archive(void* archive); 10 | contents_zip_t get_contents_zip(void* archive, const char *name, time_t *last_modified, char **error_msg); 11 | -------------------------------------------------------------------------------- /planck-cljs/script/clean: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 4 | set -x 5 | fi 6 | 7 | if [ -z "$BUILD_PPA" ]; then 8 | rm -f jscomp.js 9 | rm -f paredit.js 10 | fi 11 | rm -rf out 12 | rm -rf target 13 | rm -rf resources 14 | rm -rf tools.reader 15 | rm -rf clojurescript 16 | rm -rf .cpcache 17 | script/clean-bundle 18 | -------------------------------------------------------------------------------- /planck-cljs/test/general/fipp_test.cljs: -------------------------------------------------------------------------------- 1 | (ns general.fipp-test 2 | "A test namespace to ensure we are bundling all 3 | of the Fipp namespaces." 4 | (:require 5 | [clojure.test :refer [deftest is]] 6 | [fipp.clojure] 7 | [fipp.deque] 8 | [fipp.edn] 9 | [fipp.ednize] 10 | [fipp.engine] 11 | [fipp.visit])) 12 | 13 | (deftest nonce-test 14 | (is (= 1 1))) 15 | 16 | -------------------------------------------------------------------------------- /planck-c/bundle.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "bundle_inflate.h" 4 | 5 | char *bundle_get_contents(char *path) { 6 | fprintf(stderr, "WARN: no bundled sources, need to run script/bundle-c\n"); 7 | return NULL; 8 | } 9 | 10 | #ifdef BUNDLE_TEST 11 | int main(void) { 12 | fprintf(stderr, "no bundled sources, need to run run script/bundle-c\n"); 13 | return -1; 14 | } 15 | #endif 16 | -------------------------------------------------------------------------------- /int-test/expected/PLANCK-ERR.txt: -------------------------------------------------------------------------------- 1 | hello stderr 2 | WARNING: Use of undeclared Var cljs.user/not-symbol 3 | Argument to in-ns must be a symbol. 4 | Argument to in-ns must be a symbol. 5 | ^ 6 | WARNING: nfirst already refers to: cljs.core/nfirst being replaced by: foo.bar/nfirst at line 1 7 | Evaluating Expression 8 | ((2) + (3)) 9 | warn 10 | error 11 | nulltruea1[ 12 | 1, 13 | 2 14 | ]{ 15 | "foo": 1 16 | } -------------------------------------------------------------------------------- /planck-cljs/test/planck/environ_test.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.environ-test 2 | (:require [planck.environ :refer env] 3 | [planck.io :as io] 4 | [clojure.string :as string] 5 | [cljs.test :refer-macros [deftest is use-fixtures]])) 6 | 7 | (deftest user-home 8 | (is (and (#{:home} (keys env)) 9 | (not (string/blank? (:home env))) 10 | (io/directory? (:home env))))) 11 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/shell.clj: -------------------------------------------------------------------------------- 1 | (ns planck.shell 2 | "Planck Shell macros.") 3 | 4 | (defmacro with-sh-dir 5 | "Sets the directory for use with sh, see sh for details." 6 | [dir & forms] 7 | `(binding [planck.shell/*sh-dir* ~dir] 8 | ~@forms)) 9 | 10 | (defmacro with-sh-env 11 | "Sets the environment for use with sh, see sh for details." 12 | [env & forms] 13 | `(binding [planck.shell/*sh-env* ~env] 14 | ~@forms)) 15 | -------------------------------------------------------------------------------- /doc/legal.md: -------------------------------------------------------------------------------- 1 | ## Legal 2 | 3 | 4 | 5 | Planck™ is copyright © 2015–2024 Mike Fikes and Contributors. 6 | 7 | It is distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 8 | 9 | Planck uses several open-source libraries. You can get the details for those libraries by issuing the following at the terminal: 10 | 11 | ``` 12 | planck -l 13 | ``` 14 | 15 | -------------------------------------------------------------------------------- /script/get-closure-compiler: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 4 | set -x 5 | fi 6 | 7 | if [ ! -f compiler/closure-compiler-v$GCC_RELEASE.jar ]; then 8 | echo "Fetching Google Closure Compiler..." 9 | mkdir -p compiler 10 | cd compiler 11 | curl --retry 3 -O -s https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/v$GCC_RELEASE/closure-compiler-v$GCC_RELEASE.jar || { echo "Download failed."; exit 1; } 12 | cd ../.. 13 | fi 14 | -------------------------------------------------------------------------------- /script/test-unit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | "exec" "planck-c/build/planck" "--classpath=lib/test.check-1.1.1.jar:lib/long-3.0.3-1.jar:planck-cljs/test" "--compile-opts" "@/compile-opts.edn" "$0" "$@" 3 | (ns planck.unit-test 4 | (:require [cljs.test] 5 | [planck.test-runner] 6 | [planck.core :refer [exit]])) 7 | 8 | (defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m] 9 | (when-not (cljs.test/successful? m) 10 | (exit 1))) 11 | 12 | (planck.test-runner/run-all-tests) 13 | -------------------------------------------------------------------------------- /planck-cljs/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.clojure/clojurescript {:mvn/version "1.11.132"} 2 | org.clojure/core.rrb-vector {:mvn/version "0.1.2"} 3 | org.clojure/test.check {:mvn/version "1.1.1"} 4 | com.cognitect/transit-clj {:mvn/version "1.0.329"} 5 | com.cognitect/transit-cljs {:mvn/version "0.8.269"} 6 | com.cognitect/transit-js {:mvn/version "0.8.874"} 7 | fipp/fipp {:mvn/version "0.6.24"} 8 | malabarba/lazy-map {:mvn/version "1.3"} 9 | cljsjs/long {:mvn/version "3.0.3-1"}}} 10 | -------------------------------------------------------------------------------- /planck-cljs/test/general/transit_test.cljs: -------------------------------------------------------------------------------- 1 | (ns general.transit-test 2 | (:require 3 | [clojure.test :refer [deftest is]] 4 | [cognitect.transit :as t])) 5 | 6 | (deftest serialize-meta 7 | (let [roundtrip (fn [x] 8 | (let [w (t/writer :json 9 | {:transform t/write-meta}) 10 | r (t/reader :json)] 11 | (t/read r (t/write w x)))) 12 | x ^:foo [1 2]] 13 | (is (= x (roundtrip x))) 14 | (is (= (meta x) (meta (roundtrip x)))))) 15 | -------------------------------------------------------------------------------- /script/get-build-cache: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 4 | set -x 5 | fi 6 | 7 | if [ ! -d planck-cljs/.buildcache-$GCC_RELEASE-$GCL_RELEASE ]; then 8 | echo "Fetching Build Cache..." 9 | cd planck-cljs 10 | curl --retry 3 -O -s https://planck-repl.org/releases/buildcache-$GCC_RELEASE-$GCL_RELEASE.tar || { echo "Download failed."; exit 1; } 11 | tar -xf buildcache-$GCC_RELEASE-$GCL_RELEASE.tar 12 | echo "Cleaning up Build Cache archive..." 13 | rm buildcache-$GCC_RELEASE-$GCL_RELEASE.tar 14 | cd .. 15 | fi 16 | -------------------------------------------------------------------------------- /planck-cljs/script/decode.cljs: -------------------------------------------------------------------------------- 1 | (ns script.bootstrap.build 2 | (:require 3 | [cljs.source-map :as sm] 4 | [cognitect.transit :as transit] 5 | [planck.core :refer [slurp]] 6 | [planck.repl :refer [strip-source-map]])) 7 | 8 | (defn cljs->transit-json 9 | [x] 10 | (let [wtr (transit/writer :json)] 11 | (transit/write wtr x))) 12 | 13 | (let [file (first *command-line-args*) 14 | sm-json (slurp file) 15 | decoded (sm/decode (.parse js/JSON sm-json)) 16 | stripped (#'strip-source-map decoded) 17 | transit-json (cljs->transit-json stripped)] 18 | (println transit-json)) 19 | -------------------------------------------------------------------------------- /doc/contributing.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | 4 | 5 | The source for Planck is hosted on GitHub: [https://github.com/planck-repl/planck](https://github.com/planck-repl/planck). 6 | 7 | If you'd like to contribute, check it out, and take a look at the open issues list: [https://github.com/planck-repl/planck/issues](https://github.com/planck-repl/planck/issues). 8 | 9 | This documentation is also hosted in the Planck GitHub repository, under the `site` directory of that repository. So, if you find improvements that can be made, feel free to submit PRs! 10 | -------------------------------------------------------------------------------- /site/src/postamble.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/bundle.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.bundle 2 | "Require namespaces so they will be bundled in the Planck binary." 3 | (:require 4 | [google-closure-compiler-js] 5 | [cljs.analyzer.api] 6 | [cljs.math] 7 | [cljs.pprint] 8 | [cljs.spec.alpha] 9 | [cljs.spec.test.alpha] 10 | [cljs.test] 11 | [clojure.core.protocols] 12 | [clojure.core.reducers] 13 | [clojure.data] 14 | [clojure.datafy] 15 | [clojure.reflect] 16 | [clojure.zip] 17 | [fipp.clojure] 18 | [fipp.deque] 19 | [fipp.edn] 20 | [fipp.ednize] 21 | [fipp.engine] 22 | [fipp.visit] 23 | [planck.bundle.gcl])) 24 | -------------------------------------------------------------------------------- /doc/gcl.md: -------------------------------------------------------------------------------- 1 | ## Google Closure Library 2 | 3 | Planck bundles the majority of the [Google Closure library](https://developers.google.com/closure/library/). The following namespaces are not included: 4 | 5 | * goog.debug 6 | * goog.demos 7 | * goog.editor 8 | * goog.events 9 | * goog.fx 10 | * goog.graphics 11 | * goog.labs 12 | * goog.net.testdata 13 | * goog.testing 14 | * goog.ui 15 | 16 | These namespaces can be included in custom builds of Planck by editing the directories that are set to be ignored in the `script/get-closure-library` file. Planck will then need to be [built from source](https://cljdoc.org/d/planck/planck/CURRENT/doc/setup). 17 | -------------------------------------------------------------------------------- /site/src/preamble.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Planck ClojureScript REPL 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /doc/guide.md: -------------------------------------------------------------------------------- 1 | # Planck User Guide 2 | 3 | ## Single Page 4 | 5 | [Single Page HTML](guide-all.html) 6 | 7 | ## By Section 8 | 9 | * [Intro](intro.html) 10 | * [Setup](setup.html) 11 | * [Running](running.html) 12 | * [REPL](repl.html) 13 | * [One Liners](one-liners.html) 14 | * [Scripts](scripts.html) 15 | * [Planck Namespaces](planck-namespaces.html) 16 | * [Source Dev](source-dev.html) 17 | * [Testing](testing.html) 18 | * [Dependencies](dependencies.html) 19 | * [Performance](performance.html) 20 | * [Socket REPL](socket-repl.html) 21 | * [IDEs](ides.html) 22 | * [Internals](internals.html) 23 | * [Contributing](contributing.html) 24 | * [Legal](legal.html) 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata 2 | .DS_Store 3 | build/ 4 | lib/ 5 | .planck_history 6 | planck-cljs/sandbox-m2 7 | int-test/cache/*.js 8 | int-test/cache/*.json 9 | site/script/cache/*.js 10 | site/script/cache/*.json 11 | site/script/target 12 | site/target 13 | planck-cljs/tools.reader 14 | planck-cljs/clojurescript 15 | .idea 16 | /planck-c/cmake-build-debug 17 | /.planck_cache 18 | .vagrant 19 | build-envs/*/*-cloudimg-console.log 20 | /compiler 21 | /planck-cljs/jscomp.js 22 | /planck-cljs/paredit.js 23 | /planck-cljs/bundle.c 24 | /planck-cljs/bundle_dict.c 25 | /planck-cljs/src/planck/bundle 26 | .buildcache-* 27 | .cpcache 28 | .planck_cache 29 | /planck-man/plk.1 30 | *.swp 31 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject planck "2.28.0" 2 | :description "Stand-alone ClojureScript REPL" 3 | :url "https://planck-repl.org" 4 | :scm {:name "git" :url "https://github.com/planck-repl/planck"} 5 | :license {:name "Eclipse Public License" 6 | :url "http://www.eclipse.org/legal/epl-v10.html"} 7 | :source-paths ["planck-cljs/src"] 8 | :dependencies [[org.clojure/clojurescript "1.11.132"] 9 | [org.clojure/core.rrb-vector "0.1.2"] 10 | [fipp/fipp "0.6.24"] 11 | [malabarba/lazy-map "1.3"] 12 | [com.cognitect/transit-cljs "0.8.269"] 13 | [com.cognitect/transit-js "0.8.874"]]) 14 | -------------------------------------------------------------------------------- /doc/one-liners.md: -------------------------------------------------------------------------------- 1 | ## One Liners 2 | 3 | 4 | 5 | It is possible to use Planck directly on the command line, evaluating forms directly without entering a interactive REPL. To do this, pass `-e` or ` -​-​eval`. 6 | 7 | For example, here is a way to calculate π, based on a popular technique used in the early days with BASIC: 8 | 9 | ``` 10 | $ planck -e'(* 4 (Math/atan 1))' 11 | 3.141592653589793 12 | ``` 13 | 14 | It is also possible to use multiple evals. This prints a directory listing: 15 | 16 | ``` 17 | planck -e"(require 'planck.core)" -e'(run! (comp println :path) (planck.core/file-seq "/tmp"))' 18 | ``` 19 | -------------------------------------------------------------------------------- /site/script/deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env planck 2 | (ns deploy.core 3 | (:require [planck.core :refer [exit]] 4 | [planck.shell :refer [sh *sh-dir*]])) 5 | 6 | (def ^:dynamic *destination* nil) 7 | 8 | (defn ensure-succeeded! [x] 9 | (when-not (zero? (:exit x)) 10 | (print (:out x)) 11 | (print (:err x)) 12 | (exit (:exit x)))) 13 | 14 | (defn do! [& args] 15 | (ensure-succeeded! (apply sh args))) 16 | 17 | (binding [*destination* "planck-repl.org" 18 | *sh-dir* "target"] 19 | (do! "tar" "cf" "public.tar" "public") 20 | (do! "scp" "public.tar" (str *destination* ":")) 21 | (do! "ssh" *destination* "tar" "xf" "public.tar" "-C" "/var/planck-repl")) 22 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/environ.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.environ 2 | "Facilities for working with environment variables." 3 | (:require 4 | [clojure.string :as string] 5 | [goog.object :as gobj])) 6 | 7 | (defn- keywordize [s] 8 | (-> (string/lower-case s) 9 | (string/replace "_" "-") 10 | (string/replace "." "-") 11 | (keyword))) 12 | 13 | (defn- read-system-env [] 14 | (let [env-obj (js/PLANCK_GETENV)] 15 | (into {} (for [k (js-keys env-obj)] 16 | [(keywordize k) (gobj/get env-obj k)])))) 17 | 18 | (defonce ^{:doc 19 | "A map of environment variables. Note that keys are lowercased, with 20 | characters \"_\" and \".\" additionally replaced with \"-\"."} 21 | env 22 | (read-system-env)) 23 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/from/io/aviso/ansi.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.from.io.aviso.ansi 2 | "Help with generating textual output that includes ANSI escape codes for formatting. 3 | Ported for use with bootstrap ClojureScript in Planck from 4 | https://github.com/AvisoNovate/pretty/blob/master/src/io/aviso/ansi.clj" 5 | (:require-macros 6 | [planck.from.io.aviso.ansi :refer [generate-color-functions]]) 7 | (:require 8 | [clojure.string :as str])) 9 | 10 | (def ^:const csi 11 | "The control sequence initiator: `ESC [`" 12 | "\u001b[") 13 | 14 | ;; select graphic rendition 15 | (def ^:const sgr 16 | "The Select Graphic Rendition suffix: m" 17 | "m") 18 | 19 | (def ^:const reset-font 20 | "Resets the font, clearing bold, italic, color, and background color." 21 | (str csi sgr)) 22 | 23 | (generate-color-functions) 24 | -------------------------------------------------------------------------------- /planck-c/jsc_utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | JSStringRef to_string(JSContextRef ctx, JSValueRef val); 4 | 5 | #ifdef DEBUG 6 | #define debug_print_value(prefix, ctx, val) print_value(prefix ": ", ctx, val) 7 | #else 8 | #define debug_print_value(prefix, ctx, val) 9 | #endif 10 | 11 | void print_value(char *prefix, JSContextRef ctx, JSValueRef val); 12 | 13 | JSValueRef evaluate_script(JSContextRef ctx, char *script, char *source); 14 | 15 | char *value_to_c_string(JSContextRef ctx, JSValueRef val); 16 | 17 | char* value_to_c_string_ext(JSContextRef ctx, JSValueRef val, bool handle_non_string_values); 18 | 19 | JSValueRef c_string_to_value(JSContextRef ctx, const char *s); 20 | 21 | int array_get_count(JSContextRef ctx, JSObjectRef arr); 22 | 23 | #define array_get_value_at_index(ctx, array, i) JSObjectGetPropertyAtIndex(ctx, array, i, NULL) 24 | -------------------------------------------------------------------------------- /planck-c/file.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef unsigned long descriptor_t; 4 | 5 | descriptor_t ufile_open_read(const char *path, const char *encoding); 6 | 7 | descriptor_t ufile_open_write(const char *path, bool append, const char *encoding); 8 | 9 | JSStringRef ufile_read(descriptor_t descriptor); 10 | 11 | void ufile_write(descriptor_t descriptor, JSStringRef text); 12 | 13 | void ufile_flush(descriptor_t descriptor); 14 | 15 | void ufile_close(descriptor_t descriptor); 16 | 17 | descriptor_t file_open_read(const char *path); 18 | 19 | descriptor_t file_open_write(const char *path, bool append); 20 | 21 | size_t file_read(descriptor_t descriptor, size_t buf_size, uint8_t *buffer); 22 | 23 | void file_write(descriptor_t descriptor, size_t buf_size, uint8_t *buffer); 24 | 25 | void file_flush(descriptor_t descriptor); 26 | 27 | void file_close(descriptor_t descriptor); 28 | -------------------------------------------------------------------------------- /script/clean: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while [ $# -gt 0 ] 4 | do 5 | case "$1" in 6 | --keep-gcl) 7 | export KEEP_GCL=1 8 | shift 9 | ;; 10 | *) 11 | break 12 | ;; 13 | esac 14 | done 15 | 16 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 17 | set -x 18 | fi 19 | 20 | if [ -z "$BUILD_PPA" ]; then 21 | rm -rf compiler 22 | fi 23 | 24 | cd planck-cljs 25 | script/clean 26 | cd .. 27 | 28 | # GCL 29 | if [ -z "$BUILD_PPA" ]; then 30 | if ! [ "${KEEP_GCL:-0}" == "1" ]; then 31 | rm -rf planck-cljs/lib/closure 32 | rm -rf planck-cljs/lib/third_party 33 | fi 34 | rm -f planck-cljs/src/planck/bundle/gcl.cljs 35 | rm -rf planck-cljs/src/planck/bundle 36 | fi 37 | 38 | # CMake 39 | rm -rf planck-c/build 40 | 41 | if [ -z "$BUILD_PPA" ]; then 42 | rm -f planck-man/plk.1 43 | fi 44 | 45 | # Site 46 | cd site 47 | script/clean 48 | cd .. 49 | -------------------------------------------------------------------------------- /planck-c/str.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int str_has_suffix(const char *str, const char *suffix) { 5 | size_t len = strlen(str); 6 | size_t suffix_len = strlen(suffix); 7 | 8 | if (len < suffix_len) { 9 | return -1; 10 | } 11 | 12 | return strcmp(str + (len - suffix_len), suffix); 13 | } 14 | 15 | int str_has_prefix(const char *str, const char *prefix) { 16 | size_t len = strlen(str); 17 | size_t prefix_len = strlen(prefix); 18 | 19 | if (len < prefix_len) { 20 | return -1; 21 | } 22 | 23 | return strncmp(str, prefix, prefix_len); 24 | } 25 | 26 | char *str_concat(const char *s1, const char *s2) { 27 | size_t l1 = strlen(s1), l2 = strlen(s2); 28 | size_t len = l1 + l2 + 1; 29 | char *s = malloc(len * sizeof(char)); 30 | 31 | if (s) { 32 | memcpy(s, s1, l1); 33 | memcpy(s + l1, s2, l2 + 1); 34 | } 35 | return s; 36 | } 37 | -------------------------------------------------------------------------------- /doc/intro.md: -------------------------------------------------------------------------------- 1 | ## Intro 2 | 3 | 4 | 5 | Planck is a stand-alone ClojureScript REPL for macOS and Linux. 6 | 7 | Planck launches instantly, providing a full-featured REPL environment that is great for experimenting with and learning the ClojureScript language. 8 | 9 | Planck is also great for creating scripts in ClojureScript, providing an alternative to Bash for automating tasks. 10 | 11 | 12 | > Planck is not a ClojureScript compiler—it does not emit JavaScript for use with web browsers or other execution environments. For targeting those systems, the ClojureScript [compiler](https://clojurescript.org) along with REPLs like [Figwheel](https://github.com/bhauman/lein-figwheel) are useful. 13 | 14 | > If you are running Windows, Linux, or macOS, also be sure to check out [Lumo](https://github.com/anmonteiro/lumo), a stand-alone ClojureScript REPL based on Node.js and V8 that is capable of using NPM libraries. 15 | -------------------------------------------------------------------------------- /script/install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # Start 6 | do_usage() { 7 | echo "Installs Planck." 8 | echo -e 9 | echo "Usage:" 10 | echo "script/install [-p|--prefix ]" 11 | exit 1 12 | } 13 | 14 | default_prefix_dir="/usr/local" 15 | 16 | prefix_dir=$default_prefix_dir 17 | prefix_param=${1:-} 18 | prefix_value=${2:-} 19 | if [[ "$prefix_param" = "-p" || "$prefix_param" = "--prefix" ]]; then 20 | if [[ -z "$prefix_value" ]]; then 21 | do_usage 22 | else 23 | prefix_dir="$prefix_value" 24 | fi 25 | fi 26 | 27 | bin_dir="$prefix_dir/bin" 28 | man_dir="$prefix_dir/share/man/man1" 29 | 30 | echo "Installing planck and plk into $bin_dir" 31 | install -Dm755 planck-c/build/planck "$bin_dir/planck" 32 | install -Dm755 planck-sh/plk "$bin_dir/plk" 33 | 34 | echo "Installing man pages into $man_dir" 35 | install -Dm644 planck-man/planck.1 "$man_dir/planck.1" 36 | install -Dm644 planck-man/plk.1 "$man_dir/plk.1" 37 | 38 | echo "Use plk -h for help." 39 | -------------------------------------------------------------------------------- /site/script/build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | "exec" "plk" "-Sdeps" "{:deps {markdown-clj {:mvn/version \"0.9.85\"}}}" "$0" 3 | (ns build.core 4 | (:require [markdown.core :refer [md->html]] 5 | [planck.core :refer [slurp spit]] 6 | [planck.shell :refer [sh]])) 7 | 8 | (def src "src/") 9 | (def target "target/") 10 | (def public (str target "public/")) 11 | 12 | (def preamble (slurp (str src "preamble.html"))) 13 | (def postamble (slurp (str src "postamble.html"))) 14 | 15 | (defn wrap-html 16 | [body] 17 | (str preamble body postamble)) 18 | 19 | (defn md-to-html 20 | [in out] 21 | (spit out 22 | (wrap-html 23 | (md->html (slurp in))))) 24 | 25 | (defn process-md 26 | [in] 27 | (md-to-html 28 | (str src in) (str public (subs in 0 (- (count in) 2)) "html"))) 29 | 30 | (sh "mkdir" target) 31 | (sh "rm" "-rf" public) 32 | (sh "mkdir" public) 33 | 34 | (process-md "index.md") 35 | 36 | (sh "cp" "-r" (str src "css") public) 37 | (sh "cp" "-r" (str src "img") public) 38 | (sh "cp" "-r" (str src "js") public) 39 | -------------------------------------------------------------------------------- /site/src/index.md: -------------------------------------------------------------------------------- 1 | # Planck 2 | 3 | Planck is a stand-alone ClojureScript REPL for macOS and Linux. 4 | 5 | Planck launches instantly and is useful for scripting. 6 | 7 | 8 | 9 | You can run Clojure-idiomatic scripts with Planck: 10 | 11 | ```clojure 12 | (require '[planck.core :refer [line-seq with-open]] 13 | '[planck.io :as io] 14 | '[planck.shell :as shell]) 15 | 16 | (with-open [rdr (io/reader "input.txt")] 17 | (doseq [line (line-seq rdr)] 18 | (println (count line)))) 19 | 20 | (shell/sh "say" "done") 21 | ``` 22 | 23 | Get it: On macOS `brew install planck`, on Ubuntu [install](https://cljdoc.org/d/planck/planck/CURRENT/doc/setup) using `apt-get`. Otherwise [download](https://planck-repl.org/binaries/) a binary, or [build](https://github.com/planck-repl/planck#building) it. 24 | 25 | Online docs: [![cljdoc badge](https://cljdoc.org/badge/planck/planck)](https://cljdoc.org/d/planck/planck/CURRENT) 26 | 27 | Planck is free and [open source](https://github.com/planck-repl/planck). 28 | -------------------------------------------------------------------------------- /planck-cljs/test/general/core_test.cljs: -------------------------------------------------------------------------------- 1 | (ns general.core-test 2 | "General tests to ensure Planck is operating correctly. 3 | These tests are not testing any specific Planck namespace." 4 | (:require-macros 5 | [clojure.template :refer [do-template]] 6 | [cljs.analyzer.macros] 7 | [cljs.compiler.macros] 8 | [cljs.env.macros]) 9 | (:require 10 | [cljs.test :refer-macros [deftest is]] 11 | [cljs.pprint])) 12 | 13 | (deftest do-template-test 14 | (is (= '(do (+ 4 2) (+ 5 3)) 15 | (macroexpand '(do-template [x y] (+ y x) 2 4 3 5))))) 16 | 17 | (deftest system-timer-monkey-patch-test 18 | ; Ensure we haven't broken system-time 19 | (let [t0 (system-time) 20 | t1 (system-time)] 21 | (is (<= t0 t1)))) 22 | 23 | (deftest clojurescript-version-test 24 | (is (some? *clojurescript-version*))) 25 | 26 | (deftest macros-loaded 27 | (is (exists? cljs.analyzer.macros$macros/no-warn)) 28 | (is (exists? cljs.compiler.macros$macros/emit-wrap)) 29 | (is (exists? cljs.env.macros$macros/ensure)) 30 | (is (exists? cljs.pprint$macros/pp))) 31 | -------------------------------------------------------------------------------- /planck-c/bundle_inflate.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | int bundle_inflate(char *dest, unsigned char *src, unsigned int src_len, unsigned int len) { 8 | if (src_len == 0) { 9 | return 0; 10 | } 11 | 12 | bool done = false; 13 | int status; 14 | 15 | z_stream strm; 16 | strm.next_in = src; 17 | strm.avail_in = src_len; 18 | strm.total_out = 0; 19 | strm.zalloc = Z_NULL; 20 | strm.zfree = Z_NULL; 21 | 22 | if (inflateInit2(&strm, (15 + 32)) != Z_OK) { 23 | return -1; 24 | } 25 | 26 | while (!done) { 27 | strm.next_out = (unsigned char *) dest + strm.total_out; 28 | strm.avail_out = len - (int) strm.total_out; 29 | 30 | status = inflate(&strm, Z_SYNC_FLUSH); 31 | if (status == Z_STREAM_END) { 32 | done = true; 33 | } else if (status != Z_OK) { 34 | break; 35 | } 36 | } 37 | 38 | if (inflateEnd(&strm) != Z_OK) { 39 | return -1; 40 | } 41 | 42 | return done ? 0 : -1; 43 | } 44 | -------------------------------------------------------------------------------- /planck-cljs/script/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 4 | set -x 5 | fi 6 | 7 | if [ ! -f jscomp.js ]; then 8 | curl -s -O https://planck-repl.org/releases/closure-${CLOSURE_JS_RELEASE}/jscomp.js 9 | fi 10 | 11 | if [ ! -f paredit.js ]; then 12 | curl -s -O https://planck-repl.org/releases/paredit-${PAREDIT_JS_RELEASE}/paredit.js 13 | fi 14 | 15 | # Make sure we fail and exit on the command that actually failed. 16 | set -e 17 | set -o pipefail 18 | 19 | mkdir -p out/cljs/analyzer 20 | mkdir -p out/cljs/compiler 21 | mkdir -p out/cljs/env 22 | mkdir -p out/cljs/core/specs 23 | 24 | export PATH=script:$PATH 25 | 26 | if [ -n "${CANARY_CLOJURESCRIPT_VERSION+set}" ]; then 27 | DEPS="{:deps {org.clojure/clojurescript {:mvn/version \"$CANARY_CLOJURESCRIPT_VERSION\"}}}" 28 | fi 29 | 30 | if [ -n "${MAIN_ALIASES+set}" ]; then 31 | clojure -Sdeps "${DEPS:-{}}" -M"${MAIN_ALIASES}" script/build.clj 32 | else 33 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 34 | clojure -Sdeps "${DEPS:-{}}" -Spath 35 | fi 36 | clojure -Sdeps "${DEPS:-{}}" -M script/build.clj 37 | fi 38 | -------------------------------------------------------------------------------- /doc/cljdoc.edn: -------------------------------------------------------------------------------- 1 | {:cljdoc.doc/tree [["Readme" {:file "README.md"}] 2 | ["Changelog" {:file "CHANGELOG.md"}] 3 | ["Intro" {:file "doc/intro.md"}] 4 | ["Setup" {:file "doc/setup.md"}] 5 | ["Running" {:file "doc/running.md"}] 6 | ["REPL" {:file "doc/repl.md"}] 7 | ["One Liners" {:file "doc/one-liners.md"}] 8 | ["Scripts" {:file "doc/scripts.md"}] 9 | ["Planck Namespaces" {:file "doc/planck-namespaces.md"}] 10 | ["Closure Library" {:file "doc/gcl.md"}] 11 | ["Source Dev" {:file "doc/source-dev.md"}] 12 | ["Testing" {:file "doc/testing.md"}] 13 | ["Dependencies" {:file "doc/dependencies.md"}] 14 | ["Performance" {:file "doc/performance.md"}] 15 | ["Socket REPL" {:file "doc/socket-repl.md"}] 16 | ["IDEs" {:file "doc/ides.md"}] 17 | ["Internals" {:file "doc/internals.md"}] 18 | ["Contributing" {:file "doc/contributing.md"}] 19 | ["Legal" {:file "doc/legal.md"}]]} 20 | -------------------------------------------------------------------------------- /planck-cljs/test/planck/test_runner.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.test-runner 2 | (:require 3 | [clojure.spec.test.alpha :as st] 4 | [clojure.test :refer [run-tests]] 5 | [fipp.edn] 6 | [general.closure-libs-test] 7 | [general.cljsjs-libs-test] 8 | [general.core-test] 9 | [general.closure-defines-test] 10 | [general.data-readers-test] 11 | [general.fipp-test] 12 | [general.transit-test] 13 | [planck.closure-test] 14 | [planck.core :refer [exit]] 15 | [planck.core-test] 16 | [planck.http-test] 17 | [planck.io-test] 18 | [planck.js-deps-test] 19 | [planck.repl-test] 20 | [planck.shell-test] 21 | [planck.socket-test])) 22 | 23 | #_(st/instrument) 24 | 25 | (defn run-all-tests [] 26 | (run-tests 27 | 'planck.core-test 28 | 'planck.io-test 29 | 'planck.shell-test 30 | 'planck.socket-test 31 | 'planck.repl-test 32 | 'planck.js-deps-test 33 | 'planck.http-test 34 | 'planck.closure-test 35 | 'general.closure-libs-test 36 | 'general.cljsjs-libs-test 37 | 'general.core-test 38 | 'general.closure-defines-test 39 | 'general.data-readers-test 40 | 'general.fipp-test 41 | 'general.transit-test)) 42 | -------------------------------------------------------------------------------- /planck-c/sockets.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef void *(*connection_handler_t)(void *socket_desc); 4 | 5 | typedef void (*listen_successful_cb_t)(void); 6 | 7 | typedef struct accepted_conn_cb_ret { 8 | int err; 9 | void* info; 10 | } accepted_conn_cb_ret_t; 11 | 12 | typedef accepted_conn_cb_ret_t* (*accepted_conn_cb_t)(int sock, void* state); 13 | 14 | typedef struct conn_data_cb_ret { 15 | int err; 16 | bool close; 17 | } conn_data_cb_ret_t; 18 | 19 | typedef conn_data_cb_ret_t* (*conn_data_cb_t)(char* data, int sock, void* state); 20 | 21 | typedef struct socket_accept_info { 22 | char *host; 23 | int port; 24 | listen_successful_cb_t listen_successful_cb; 25 | accepted_conn_cb_t accepted_conn_cb; 26 | conn_data_cb_t conn_data_cb; 27 | int socket_desc; 28 | void* info; 29 | } socket_accept_info_t; 30 | 31 | int write_to_socket(int fd, const char *text); 32 | 33 | int bind_and_listen(socket_accept_info_t* socket_accept_info1); 34 | 35 | void *accept_connections(void *data); 36 | 37 | int close_socket(int fd); 38 | 39 | int connect_socket(const char *host, int port, conn_data_cb_t conn_data_cb, 40 | void *data_arrived_info); 41 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/from/cljs/core.cljs: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Rich Hickey. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc planck.from.cljs.core) 10 | 11 | ;; Extracted from cljs.core/pr-writer-impl 12 | (defn date->str [obj] 13 | (let [normalize (fn [n len] 14 | (loop [ns (str n)] 15 | (if (< (count ns) len) 16 | (recur (str "0" ns)) 17 | ns)))] 18 | (str 19 | (str (.getUTCFullYear obj)) "-" 20 | (normalize (inc (.getUTCMonth obj)) 2) "-" 21 | (normalize (.getUTCDate obj) 2) "T" 22 | (normalize (.getUTCHours obj) 2) ":" 23 | (normalize (.getUTCMinutes obj) 2) ":" 24 | (normalize (.getUTCSeconds obj) 2) "." 25 | (normalize (.getUTCMilliseconds obj) 3) "-" 26 | "00:00"))) 27 | -------------------------------------------------------------------------------- /script/test-core: -------------------------------------------------------------------------------- 1 | #!planck-c/build/planck -s --classpath=planck-cljs/clojurescript/src/test/cljs 2 | (ns planck.test-core 3 | (:require [cljs.test :refer-macros [run-tests]] 4 | [cljs.core-test :as core-test] 5 | [cljs.reader-test] 6 | [cljs.binding-test] 7 | #_[cljs.ns-test] 8 | [clojure.string-test] 9 | [clojure.data-test] 10 | [cljs.macro-test] 11 | [cljs.letfn-test] 12 | [foo.ns-shadow-test] 13 | [cljs.top-level] 14 | [cljs.reducers-test] 15 | #_[cljs.keyword-test] 16 | [cljs.import-test] 17 | [cljs.ns-test.foo] 18 | #_[cljs.pprint] 19 | [planck.core :refer [exit]])) 20 | 21 | (defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m] 22 | (when-not (cljs.test/successful? m) 23 | (exit 1))) 24 | 25 | (run-tests 26 | 'cljs.core-test 27 | 'cljs.reader-test 28 | 'clojure.string-test 29 | 'clojure.data-test 30 | 'cljs.letfn-test 31 | 'cljs.reducers-test 32 | 'cljs.binding-test 33 | 'cljs.macro-test 34 | 'cljs.top-level 35 | #_'cljs.keyword-test 36 | #_'cljs.ns-test 37 | 'cljs.ns-test.foo 38 | 'foo.ns-shadow-test 39 | 'cljs.import-test 40 | #_'cljs.pprint) 41 | -------------------------------------------------------------------------------- /planck-c/globals.h: -------------------------------------------------------------------------------- 1 | // Global variables used throughout Planck 2 | 3 | #define PLANCK_VERSION "2.28.0" 4 | 5 | // Configuration 6 | 7 | struct src_path { 8 | char *type; 9 | char *path; 10 | void *archive; 11 | bool blacklisted; 12 | }; 13 | 14 | struct script { 15 | char *type; 16 | bool expression; 17 | char *source; 18 | }; 19 | 20 | struct config { 21 | bool verbose; 22 | bool quiet; 23 | bool is_tty; 24 | bool repl; 25 | bool javascript; 26 | char* checked_arrays; 27 | bool static_fns; 28 | bool fn_invoke_direct; 29 | bool elide_asserts; 30 | char* optimizations; 31 | const char *theme; 32 | bool dumb_terminal; 33 | 34 | char *main_ns_name; 35 | size_t num_rest_args; 36 | char **rest_args; 37 | 38 | char *out_path; 39 | char *cache_path; 40 | 41 | size_t num_src_paths; 42 | struct src_path *src_paths; 43 | size_t num_scripts; 44 | struct script *scripts; 45 | 46 | char *socket_repl_host; 47 | int socket_repl_port; 48 | 49 | char *clojurescript_version; 50 | 51 | size_t num_compile_opts; 52 | char **compile_opts; 53 | }; 54 | 55 | extern struct config config; 56 | 57 | // Mutable variables 58 | 59 | extern int exit_value; 60 | extern bool return_termsize; 61 | -------------------------------------------------------------------------------- /site/src/css/highlightjs.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original highlight.js style (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #F0F0F0; 12 | } 13 | 14 | .hljs, 15 | .hljs-subst { 16 | color: #444; 17 | } 18 | 19 | .hljs-keyword, 20 | .hljs-attribute, 21 | .hljs-selector-tag, 22 | .hljs-meta-keyword, 23 | .hljs-doctag, 24 | .hljs-name { 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-built_in, 29 | .hljs-literal, 30 | .hljs-bullet, 31 | .hljs-code, 32 | .hljs-addition { 33 | color: #1F811F; 34 | } 35 | 36 | .hljs-regexp, 37 | .hljs-symbol, 38 | .hljs-variable, 39 | .hljs-template-variable, 40 | .hljs-link, 41 | .hljs-selector-attr, 42 | .hljs-selector-pseudo { 43 | color: #BC6060; 44 | } 45 | 46 | .hljs-type, 47 | .hljs-string, 48 | .hljs-number, 49 | .hljs-selector-id, 50 | .hljs-selector-class, 51 | .hljs-quote, 52 | .hljs-template-tag, 53 | .hljs-deletion { 54 | color: #880000; 55 | } 56 | 57 | .hljs-title, 58 | .hljs-section { 59 | color: #880000; 60 | font-weight: bold; 61 | } 62 | 63 | .hljs-comment { 64 | color: #888888; 65 | } 66 | 67 | .hljs-meta { 68 | color: #2B6EA1; 69 | } 70 | 71 | .hljs-emphasis { 72 | font-style: italic; 73 | } 74 | 75 | .hljs-strong { 76 | font-weight: bold; 77 | } 78 | -------------------------------------------------------------------------------- /planck-cljs/test/planck/socket_test.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.socket-test 2 | (:require 3 | [clojure.string :as string] 4 | [clojure.test :refer [deftest is testing async]] 5 | [planck.shell :as shell] 6 | [planck.socket :as socket])) 7 | 8 | (defn darwin? [] 9 | (= "Darwin" (-> (shell/sh "uname") :out string/trim-newline))) 10 | 11 | ;; Start up an echo server 12 | 13 | (def echo-server-port 55555) 14 | 15 | (when-not (darwin?) 16 | (socket/listen echo-server-port 17 | (fn [socket] 18 | (fn [socket data] 19 | (socket/write socket data))))) 20 | 21 | (defn latch [m f] 22 | (let [r (atom 0)] 23 | (add-watch r :latch 24 | (fn [_ _ o n] 25 | (when (== n m) (f)))) 26 | r)) 27 | 28 | (defn inc! [r] 29 | (swap! r inc)) 30 | 31 | #_(deftest listen-protected-port 32 | (when-not (darwin?) 33 | (is (thrown-with-msg? js/Error #"Permission denied" (socket/listen 123 (fn [_] (fn [_ _]))))))) 34 | 35 | #_(deftest integration-test 36 | (async done 37 | (let [l (latch 1 done)] 38 | (let [data-handler (fn [socket data] 39 | (is (= "hello" data)) 40 | (socket/close socket) 41 | (inc! l)) 42 | s (socket/connect "localhost" echo-server-port data-handler)] 43 | (socket/write s "hi"))))) 44 | -------------------------------------------------------------------------------- /planck-c/clock.c: -------------------------------------------------------------------------------- 1 | #include "clock.h" 2 | #include "engine.h" 3 | #include 4 | #include 5 | 6 | #if __DARWIN_UNIX03 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #endif 13 | 14 | uint64_t system_time() { 15 | #if __DARWIN_UNIX03 16 | static mach_timebase_info_data_t sTimebaseInfo; 17 | uint64_t now = mach_absolute_time(); 18 | if (sTimebaseInfo.denom == 0) { 19 | (void) mach_timebase_info(&sTimebaseInfo); 20 | } 21 | return now * sTimebaseInfo.numer / sTimebaseInfo.denom; 22 | #else 23 | struct timespec ts; 24 | clock_gettime(CLOCK_MONOTONIC, &ts); 25 | return 1000000000ll * ts.tv_sec + ts.tv_nsec; 26 | #endif 27 | } 28 | 29 | static uint64_t launch_time = 0; 30 | static uint64_t last_display = 0; 31 | 32 | void init_launch_timing() { 33 | launch_time = system_time(); 34 | last_display = launch_time; 35 | } 36 | 37 | void display_launch_timing(const char *label) { 38 | if (launch_time) { 39 | uint64_t now = system_time(); 40 | uint64_t total_elapsed = now - launch_time; 41 | uint64_t elapsed = now - last_display; 42 | last_display = now; 43 | char buffer[1024]; 44 | snprintf(buffer, 1024, "%50s: %10.6f %10.6f\n", label, 1e-6 * elapsed, 1e-6 * total_elapsed); 45 | engine_print(buffer); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/build_test_planck.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test Planck 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build_macos: 11 | runs-on: macos-12 12 | 13 | steps: 14 | - uses: actions/checkout@v3.0.2 15 | 16 | - uses: actions/setup-java@v3.4.1 17 | with: 18 | distribution: 'temurin' 19 | java-version: '8' 20 | 21 | - name: Install Clojure 22 | uses: DeLaGuardo/setup-clojure@9.4 23 | with: 24 | cli: latest 25 | 26 | - name: Build and Test Planck 27 | run: | 28 | script/build -Werror --fast 29 | script/test 30 | 31 | build_ubuntu: 32 | runs-on: ubuntu-22.04 33 | 34 | steps: 35 | - uses: actions/checkout@v3.0.2 36 | 37 | - uses: actions/setup-java@v3.4.1 38 | with: 39 | distribution: 'temurin' 40 | java-version: '8' 41 | 42 | - name: Install Clojure 43 | uses: DeLaGuardo/setup-clojure@9.4 44 | with: 45 | cli: latest 46 | 47 | - name: Install deps 48 | run: | 49 | sudo apt-get update 50 | sudo apt-get install -y libjavascriptcoregtk-4.0 libglib2.0-dev libzip-dev libcurl4-gnutls-dev libicu-dev unzip 51 | 52 | - name: Build and Test Planck 53 | run: | 54 | script/build -Werror --fast 55 | script/test 56 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/core.clj: -------------------------------------------------------------------------------- 1 | (ns planck.core 2 | "Core Planck macros." 3 | (:refer-clojure :exclude [with-open with-in-str])) 4 | 5 | (defmacro with-open 6 | "bindings => [name IClosable ...] 7 | 8 | Evaluates body in a try expression with names bound to the values of the 9 | inits, and a finally clause that calls `(-close name)` on each name in reverse 10 | order." 11 | [bindings & body] 12 | ;; when http://dev.clojure.org/jira/browse/CLJS-1551 lands, 13 | ;; replace with assert-args 14 | (when-not (vector? bindings) 15 | (throw (ex-info "with-open requires a vector for its bindings" {}))) 16 | (when-not (even? (count bindings)) 17 | (throw (ex-info "with-open requires an even number of forms in binding vector" {}))) 18 | (cond 19 | (= (count bindings) 0) `(do ~@body) 20 | (symbol? (bindings 0)) `(let ~(subvec bindings 0 2) 21 | (try 22 | (with-open ~(subvec bindings 2) ~@body) 23 | (finally 24 | (planck.core/-close ~(bindings 0))))) 25 | :else (throw (ex-info 26 | "with-open only allows symbols in bindings" {})))) 27 | 28 | (defmacro with-in-str 29 | "Evaluates body in a context in which `*in*` is bound to a fresh 30 | string reader initialized with the string `s`." 31 | [s & body] 32 | `(with-open [s# (#'planck.core/make-string-reader ~s)] 33 | (binding [planck.core/*in* s#] 34 | ~@body))) 35 | -------------------------------------------------------------------------------- /planck-c/tasks.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "tasks.h" 3 | 4 | static int tasks_outstanding = 0; 5 | pthread_mutex_t tasks_complete_lock = PTHREAD_MUTEX_INITIALIZER; 6 | pthread_cond_t tasks_complete_cond = PTHREAD_COND_INITIALIZER; 7 | 8 | int block_until_tasks_complete() { 9 | int err = pthread_mutex_lock(&tasks_complete_lock); 10 | if (err) return err; 11 | 12 | while (tasks_outstanding) { 13 | err = pthread_cond_wait(&tasks_complete_cond, &tasks_complete_lock); 14 | if (err) { 15 | pthread_mutex_unlock(&tasks_complete_lock); 16 | return err; 17 | } 18 | } 19 | 20 | return pthread_mutex_unlock(&tasks_complete_lock); 21 | } 22 | 23 | int signal_task_started() { 24 | int err = pthread_mutex_lock(&tasks_complete_lock); 25 | if (err) return err; 26 | 27 | tasks_outstanding++; 28 | 29 | err = pthread_cond_signal(&tasks_complete_cond); 30 | if (err) { 31 | pthread_mutex_unlock(&tasks_complete_lock); 32 | return err; 33 | } 34 | 35 | return pthread_mutex_unlock(&tasks_complete_lock); 36 | } 37 | 38 | int signal_task_complete() { 39 | int err = pthread_mutex_lock(&tasks_complete_lock); 40 | if (err) return err; 41 | 42 | tasks_outstanding--; 43 | 44 | err = pthread_cond_signal(&tasks_complete_cond); 45 | if (err) { 46 | pthread_mutex_unlock(&tasks_complete_lock); 47 | return err; 48 | } 49 | 50 | return pthread_mutex_unlock(&tasks_complete_lock); 51 | } 52 | -------------------------------------------------------------------------------- /planck-c/engine.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern JSGlobalContextRef ctx; 4 | 5 | void acquire_eval_lock(); 6 | 7 | void release_eval_lock(); 8 | 9 | void set_int_handler(); 10 | 11 | void clear_int_handler(); 12 | 13 | bool should_keep_running(); 14 | 15 | void evaluate_source(char *type, char *source_value, bool expression, bool print_nil, char *set_ns, 16 | const char *theme, bool block_until_ready, int session_id); 17 | 18 | char *munge(char *s); 19 | 20 | void bootstrap(char *out_path); 21 | 22 | int block_until_engine_ready(); 23 | 24 | extern const char *block_until_engine_ready_failed_msg; 25 | 26 | JSObjectRef get_function(char *namespace, char *name); 27 | 28 | void run_main_in_ns(char *ns, size_t argc, char **argv); 29 | 30 | void run_main_cli_fn(); 31 | 32 | char *get_current_ns(); 33 | 34 | char **get_completions(const char *buffer, int *num_completions); 35 | 36 | extern bool engine_ready; 37 | 38 | void engine_init(); 39 | 40 | void engine_shutdown(); 41 | 42 | void engine_perror(const char *msg); 43 | 44 | void engine_print_err_message(const char *msg, int err); 45 | 46 | void engine_print(const char *msg); 47 | 48 | void engine_println(const char *msg); 49 | 50 | void set_print_sender(void (*sender)(const char *msg)); 51 | 52 | bool engine_print_newline(); 53 | 54 | char *is_readable(char *expression); 55 | 56 | int indent_space_count(char *text); 57 | 58 | void highlight_coords_for_pos(int pos, const char *buf, size_t num_previous_lines, 59 | char **previous_lines, int *num_lines_up, int *highlight_pos); 60 | -------------------------------------------------------------------------------- /planck-cljs/test/planck/closure_test.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.closure-test 2 | (:require 3 | [clojure.test :refer [deftest is]] 4 | [planck.closure :as closure])) 5 | 6 | (deftest compilation 7 | (let [source "function foo$core$square_inc(long_variable){return ((long_variable * long_variable) + (1));}"] 8 | (is (= {:source "function foo$core$square_inc(long_variable){return long_variable*long_variable+1};"} 9 | (closure/compile {:name "test" :source source :optimizations :whitespace}))) 10 | (is (= {:source "function foo$core$square_inc(a){return a*a+1};"} 11 | (closure/compile {:name "test" :source source :optimizations :simple}))))) 12 | 13 | (deftest source-map 14 | (let [input {:name foo.core, :source "goog.provide(\"foo.core\");\nfoo.core.x = (1);", 15 | :sm-data {:source-map {2 {0 [{:gcol 0, :gline 1} {:gcol 13, :gline 1}], 16 | 5 [{:gcol 0, :gline 1, :name "foo.core/x"}]}}, 17 | :gen-col 0, 18 | :gen-line 2} 19 | :optimizations :whitespace} 20 | output-source-map {0 {24 [{:line 2, :col 0, :name "foo"} {:line 2, :col 5, :name "foo"}], 21 | 28 [{:line 2, :col 0, :name "core"} {:line 2, :col 5, :name "core"}], 22 | 33 [{:line 2, :col 0, :name "x"} {:line 2, :col 5, :name "x"}], 23 | 35 [{:line 2, :col 0, :name nil} {:line 2, :col 5, :name nil}]}}] 24 | (is (= output-source-map (:source-map (closure/compile input)))))) 25 | -------------------------------------------------------------------------------- /doc/setup.md: -------------------------------------------------------------------------------- 1 | ## Setup 2 | 3 | 4 | 5 | Planck runs on macOS and Linux. 6 | 7 | Planck requires no external dependencies. (There is no need for either the Java JVM or Node.js.) 8 | 9 | ### Homebrew 10 | 11 | #### Install 12 | 13 | The easiest way to install Planck on macOS is via [Homebrew](https://brew.sh): 14 | 15 | ```sh 16 | brew install planck 17 | ``` 18 | 19 | On Ubuntu you can use `apt-get`: 20 | 21 | ```sh 22 | sudo add-apt-repository ppa:mfikes/planck 23 | sudo apt-get update 24 | sudo apt-get install planck 25 | ``` 26 | 27 | For other Linux distros, [binary downloads](https://planck-repl.org/binaries/) are available for some distros. Otherwise, see Building below. 28 | 29 | #### Install Master 30 | 31 | If you'd like to use Homebrew to install the latest unreleased version of Planck (directly from master in the GitHub repository), you can do the following: 32 | 33 | ```sh 34 | brew remove planck 35 | brew install --HEAD planck 36 | ``` 37 | 38 | #### Building 39 | 40 | To build Planck on Linux or macOS, get a copy of the source tree, install the [needed dependencies](https://github.com/planck-repl/planck/wiki/Building) and run 41 | 42 | ```sh 43 | script/build 44 | ``` 45 | 46 | This results in a binary being placed in `planck-c/build`. 47 | 48 | You can then optionally install Planck, the `plk` script, and associated man pages via 49 | 50 | ```sh 51 | script/install 52 | ``` 53 | 54 | ### Bug Reporting 55 | 56 | If you happen to encounter any issues with Planck, issues are tracked on GitHub at [https://github.com/planck-repl/planck/issues](https://github.com/planck-repl/planck/issues). 57 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/themes.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.themes 2 | "Planck color theme management." 3 | (:require 4 | [planck.from.io.aviso.ansi :as ansi])) 5 | 6 | (def ^:private colorize-fn-dumb identity) 7 | (def ^:private ^:const colorize-off-dumb "") 8 | 9 | (def ^:private dumb 10 | {:results-font colorize-off-dumb 11 | :results-string-font colorize-off-dumb 12 | :results-keyword-font colorize-off-dumb 13 | :ex-msg-fn colorize-fn-dumb 14 | :ex-stack-fn colorize-fn-dumb 15 | :rdr-ann-err-fn colorize-fn-dumb 16 | :err-font colorize-off-dumb 17 | :verbose-font colorize-off-dumb 18 | :reset-font colorize-off-dumb}) 19 | 20 | (def ^:private theme-ansi-base 21 | {:reset-font ansi/reset-font}) 22 | 23 | (def ^:private light 24 | (merge theme-ansi-base 25 | {:results-font ansi/blue-font 26 | :results-string-font ansi/green-font 27 | :results-keyword-font ansi/magenta-font 28 | :ex-msg-fn ansi/bold-red 29 | :ex-stack-fn ansi/green 30 | :rdr-ann-err-fn ansi/bold-magenta 31 | :err-font ansi/red-font 32 | :verbose-font ansi/white-font})) 33 | 34 | (def ^:private dark 35 | (merge theme-ansi-base 36 | {:results-font ansi/bold-blue-font 37 | :results-string-font ansi/green-font 38 | :results-keyword-font ansi/magenta-font 39 | :ex-msg-fn ansi/bold-red 40 | :ex-stack-fn ansi/green 41 | :rdr-ann-err-fn ansi/magenta 42 | :err-font ansi/red-font 43 | :verbose-font ansi/white-font})) 44 | 45 | (def ^:private themes 46 | {:plain dumb 47 | :light light 48 | :dark dark}) 49 | 50 | (defn get-theme 51 | [theme-id] 52 | (get themes theme-id dumb)) 53 | -------------------------------------------------------------------------------- /planck-c/timers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "timers.h" 7 | #include "engine.h" 8 | #include "tasks.h" 9 | 10 | struct timer_data_t { 11 | long millis; 12 | timer_callback_t timer_callback; 13 | void *data; 14 | }; 15 | 16 | void *timer_thread(void *data) { 17 | 18 | struct timer_data_t *timer_data = data; 19 | 20 | struct timespec t; 21 | t.tv_sec = timer_data->millis / 1000; 22 | t.tv_nsec = 1000 * 1000 * (timer_data->millis % 1000); 23 | if (t.tv_sec == 0 && t.tv_nsec == 0) { 24 | t.tv_nsec = 1; /* Evidently needed on Ubuntu 14.04 */ 25 | } 26 | 27 | int err; 28 | while ((err = nanosleep(&t, &t)) && errno == EINTR) {} 29 | if (err) { 30 | free(data); 31 | engine_perror("timer nanosleep"); 32 | return NULL; 33 | } 34 | 35 | timer_data->timer_callback(timer_data->data); 36 | 37 | free(data); 38 | 39 | return NULL; 40 | } 41 | 42 | int start_timer(long millis, timer_callback_t timer_callback, void *data) { 43 | 44 | struct timer_data_t *timer_data = malloc(sizeof(struct timer_data_t)); 45 | if (!timer_data) return -1; 46 | 47 | timer_data->millis = millis; 48 | timer_data->timer_callback = timer_callback; 49 | timer_data->data = data; 50 | 51 | pthread_attr_t attr; 52 | int err = pthread_attr_init(&attr); 53 | if (err) { 54 | free(timer_data); 55 | return err; 56 | } 57 | 58 | err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 59 | if (err) { 60 | free(timer_data); 61 | return err; 62 | } 63 | 64 | pthread_t thread; 65 | err = pthread_create(&thread, &attr, timer_thread, timer_data); 66 | if (err) { 67 | free(timer_data); 68 | } 69 | return err; 70 | } 71 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/repl.clj: -------------------------------------------------------------------------------- 1 | (ns planck.repl 2 | "Macros for use at the Planck REPL.") 3 | 4 | (defmacro apropos 5 | "Given a regular expression or stringable thing, return a seq of all public 6 | definitions in all currently-loaded namespaces that match the str-or-pattern." 7 | [str-or-pattern] 8 | `(planck.repl/apropos* '~str-or-pattern)) 9 | 10 | (defmacro dir 11 | "Prints a sorted directory of public vars in a namespace" 12 | [nsname] 13 | `(planck.repl/dir* '~nsname)) 14 | 15 | (defmacro find-doc 16 | "Prints documentation for any var whose documentation or name contains a 17 | match for re-string-or-pattern" 18 | [re-string-or-pattern] 19 | `(planck.repl/find-doc* '~re-string-or-pattern)) 20 | 21 | (defmacro doc 22 | "Prints documentation for a var or special form given its name" 23 | [sym] 24 | `(planck.repl/doc* '~sym)) 25 | 26 | (defmacro source 27 | "Prints the source code for the given symbol, if it can find it. This 28 | requires that the symbol resolve to a Var defined in a namespace for which 29 | the source is available. 30 | 31 | Example: (source filter)" 32 | [sym] 33 | `(planck.repl/source* '~sym)) 34 | 35 | (defmacro pst 36 | "Prints a stack trace of the exception. 37 | 38 | If none supplied, uses the root cause of the most recent repl exception (*e)" 39 | ([] 40 | `(planck.repl/pst*)) 41 | ([e] 42 | `(planck.repl/pst* '~e))) 43 | 44 | (defmacro ^:private with-err-str 45 | "Evaluates exprs in a context in which *print-err-fn* is bound to .append on 46 | a fresh StringBuffer. Returns the string created by any nested printing 47 | calls." 48 | [& body] 49 | `(let [sb# (js/goog.string.StringBuffer.)] 50 | (binding [cljs.core/*print-newline* true 51 | cljs.core/*print-err-fn* (fn [x#] (.append sb# x#))] 52 | ~@body) 53 | (str sb#))) 54 | 55 | (defmacro ^:private make-fn-syms 56 | [& body] 57 | (let [syms# body] 58 | `(zipmap [~@body] '~syms#))) 59 | -------------------------------------------------------------------------------- /doc/socket-repl.md: -------------------------------------------------------------------------------- 1 | ## Socket REPL 2 | 3 | 4 | 5 | Planck supports connecting via a TCP socket. 6 | 7 | This mimics the Socket REPL feature introduced with Clojure 1.8. 8 | 9 | To start Planck in this mode, add the `-n`, or `-​-​socket-repl` command line option, minimally specifying the port to listen on. If you'd like to have Planck additionally listen only on a specific IP address, specify it as in `192.0.2.1:9999`. 10 | 11 | Here is an example of starting a REPL with a listening socket enabled and `def`ing a var: 12 | 13 | ```sh 14 | $ planck -n 9999 15 | Planck socket REPL listening. 16 | cljs.user=> (def a 3) 17 | #'cljs.user/a 18 | cljs.user=> 19 | ``` 20 | 21 | At this point, Planck is running. You can use the REPL directly in the terminal. But, you can additionally make TCP connections to it and access the same vars and general runtime environment: 22 | 23 | ```sh 24 | $ telnet 0 9999 25 | Trying 0.0.0.0... 26 | Connected to localhost. 27 | Escape character is '^]'. 28 | cljs.user=> a 29 | 3 30 | cljs.user=> 31 | ``` 32 | 33 | You can make as many connections as you'd like. 34 | 35 | Each connection will have dedicated copies of certain session-centric vars, like `*1`, `*2`, `*3`, `*e`, as well as global vars that control assertions and printing. (The official Clojure Socket REPL capability also provides this sort of session isolation.) 36 | 37 | You can exit a socket REPL connection by typing `:repl/quit`, `exit`, `quit`, or `:cljs/quit`. 38 | 39 | Socket REPLs can be used by IDEs, for example. It provides a side channel that an IDE can use in order to introspect the runtime environment without interfering with your primary REPL session. 40 | 41 | Additionally, socket REPLs could be used in other creative fashions—perhaps facilitating collaborative development without relying on other sharing technologies like `tmux`. 42 | 43 | Since socket REPLs are established from environments with unknown terminal capabilities, all of the rich terminal control and coloring (VT-100 and ANSI codes) are turned off for socket REPL sessions. 44 | -------------------------------------------------------------------------------- /planck-c/edn.h: -------------------------------------------------------------------------------- 1 | #ifndef CLJ_H 2 | #define CLJ_H 3 | 4 | #include 5 | #include //TODO: Only needed privately? 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | typedef enum clj_result { 12 | CLJ_EOF = 1, 13 | CLJ_MORE = 2, 14 | CLJ_UNEXPECTED_EOF = -1, 15 | CLJ_UNMATCHED_DELIMITER = -2, 16 | CLJ_NOT_IMPLEMENTED = -3, 17 | CLJ_UNREADABLE = -4, 18 | } clj_Result; 19 | 20 | int clj_is_error(clj_Result result); 21 | 22 | //typedef struct clj_named { 23 | // const wchar_t *ns; 24 | // const wchar_t *name; 25 | //} clj_Named; 26 | 27 | typedef enum clj_type { 28 | // Flags 29 | CLJ_ATOMIC = 0x0100, 30 | CLJ_COMPOSITE = 0x0200, 31 | CLJ_END = 0x1000, 32 | // Atomic types 33 | CLJ_NUMBER = 0x0101, 34 | CLJ_CHARACTER = 0x0102, 35 | CLJ_STRING = 0x0103, 36 | CLJ_KEYWORD = 0x0104, 37 | CLJ_SYMBOL = 0x0105, 38 | CLJ_REGEX = 0x0106, 39 | // Composite types 40 | CLJ_MAP = 0x0201, 41 | CLJ_LIST = 0x0202, 42 | CLJ_SET = 0x0203, 43 | CLJ_VECTOR = 0x0204, 44 | } clj_Type; 45 | 46 | int clj_is_atomic(clj_Type type); 47 | int clj_is_composite(clj_Type type); 48 | int clj_is_begin(clj_Type type); 49 | int clj_is_end(clj_Type type); 50 | 51 | typedef struct clj_node { 52 | clj_Type type; 53 | const wchar_t *value; 54 | } clj_Node; 55 | 56 | typedef struct clj_reader { 57 | // Read/write 58 | wint_t (*getwchar)(const struct clj_reader*); 59 | void (*emit)(const struct clj_reader*, const clj_Node*); 60 | void *data; 61 | // Read-only 62 | int line; 63 | int column; 64 | int depth; 65 | // Private 66 | int _discard; 67 | wint_t _readback; 68 | wint_t _readback_column; 69 | jmp_buf _fail; 70 | } clj_Reader; 71 | 72 | clj_Result clj_read(clj_Reader*); 73 | 74 | int clj_read_error(char*, const clj_Reader*, clj_Result); 75 | 76 | typedef struct clj_printer { 77 | wint_t (*putwchar)(wchar_t c); 78 | //TODO: line/column/depth 79 | } clj_Printer; 80 | 81 | void clj_print(clj_Printer*, const clj_Node*); 82 | 83 | #ifdef __cplusplus 84 | } 85 | #endif 86 | 87 | #endif /* CLJ_H */ 88 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/console.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.console 2 | "Implementation of js/console" 3 | (:refer-clojure :exclude [time])) 4 | 5 | ;; Base logging functions 6 | 7 | (defn- log-stdout [& args] 8 | (apply js/PLANCK_CONSOLE_STDOUT args)) 9 | 10 | (defn- log-stderr [& args] 11 | (apply js/PLANCK_CONSOLE_STDERR args)) 12 | 13 | ;; Timing functions 14 | ;; See https://console.spec.whatwg.org/#timing 15 | 16 | (def ^:private timer-state (atom {})) 17 | 18 | (defn- add-label [state label start-time] 19 | (let [label-exists? (contains? (:timer-table state) label)] 20 | (cond-> state 21 | true (assoc :label-exists? label-exists?) 22 | (not label-exists?) (assoc-in [:timer-table label] start-time)))) 23 | 24 | (defn- time [label] 25 | (let [new-state (swap! timer-state add-label label (system-time))] 26 | (when (:label-exists? new-state) 27 | (log-stderr "label" label "already exists")))) 28 | 29 | (defn- format-duration [d] 30 | (str (.toFixed d 2) " ms")) 31 | 32 | (defn- timer-prefix [label start-time] 33 | (str label ": " (format-duration (- (system-time) start-time)))) 34 | 35 | (defn- maybe-log-timing [label start-time & data] 36 | (if (some? start-time) 37 | (apply log-stdout (timer-prefix label start-time) data) 38 | (log-stderr "label" label "does not exist"))) 39 | 40 | (defn- time-log [label & data] 41 | (apply maybe-log-timing label (get-in @timer-state [:timer-table label]) data)) 42 | 43 | (defn- remove-label [state label] 44 | (-> state 45 | (assoc :start-time (get-in state [:timer-table label])) 46 | (update :timer-table dissoc label))) 47 | 48 | (defn- time-end [label & data] 49 | (let [new-state (swap! timer-state remove-label label)] 50 | (apply maybe-log-timing label (:start-time new-state) data))) 51 | 52 | ;; The global js/console object 53 | 54 | (set! js/console 55 | #js {:log log-stdout 56 | :trace log-stdout 57 | :debug log-stdout 58 | :info log-stdout 59 | :warn log-stderr 60 | :error log-stderr 61 | :time time 62 | :timeLog time-log 63 | :timeEnd time-end}) 64 | -------------------------------------------------------------------------------- /planck-c/archive.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "archive.h" 6 | #include "engine.h" 7 | 8 | #ifndef ZIP_RDONLY 9 | typedef struct zip zip_t; 10 | typedef struct zip_stat zip_stat_t; 11 | typedef struct zip_file zip_file_t; 12 | #define ZIP_RDONLY 16 13 | #endif 14 | 15 | void format_zip_error(const char *prefix, zip_t *zip, char **error_msg); 16 | 17 | void* open_archive(const char *path, char **error_msg) { 18 | zip_t *archive = zip_open(path, ZIP_RDONLY, NULL); 19 | 20 | if (archive == NULL && error_msg) { 21 | *error_msg = malloc(1024); 22 | if (*error_msg) { 23 | snprintf(*error_msg, 1024, "Could not open %s", path); 24 | } 25 | return NULL; 26 | } 27 | 28 | return archive; 29 | } 30 | 31 | void close_archive(void* archive) { 32 | zip_close(archive); 33 | } 34 | 35 | contents_zip_t get_contents_zip(void* archive_p, const char *name, time_t *last_modified, char **error_msg) { 36 | contents_zip_t rv; 37 | rv.payload = NULL; 38 | rv.length = 0; 39 | 40 | zip_t *archive = archive_p; 41 | 42 | zip_stat_t stat; 43 | if (zip_stat(archive, name, 0, &stat) < 0) { 44 | return rv; 45 | } 46 | 47 | zip_file_t *f = zip_fopen(archive, name, 0); 48 | if (f == NULL) { 49 | if (error_msg) { 50 | format_zip_error("zip_fopen", archive, error_msg); 51 | } 52 | return rv; 53 | } 54 | 55 | if (last_modified != NULL) { 56 | *last_modified = stat.mtime; 57 | } 58 | 59 | uint8_t *buf = malloc(stat.size + 1); 60 | if (!buf) { 61 | if (error_msg) { 62 | *error_msg = strdup("zip malloc"); 63 | } 64 | goto close_f; 65 | } 66 | 67 | if (zip_fread(f, buf, stat.size) < 0) { 68 | if (error_msg) { 69 | format_zip_error("zip_fread", archive, error_msg); 70 | } 71 | goto free_buf; 72 | } 73 | // NULL-terminate in case client wants to treat contents as a string 74 | buf[stat.size] = '\0'; 75 | 76 | zip_fclose(f); 77 | 78 | rv.payload = buf; 79 | rv.length = stat.size; 80 | 81 | return rv; 82 | 83 | free_buf: 84 | free(buf); 85 | 86 | close_f: 87 | zip_fclose(f); 88 | 89 | return rv; 90 | } 91 | 92 | void format_zip_error(const char *prefix, zip_t *zip, char **error_msg) { 93 | *error_msg = malloc(1024); 94 | if (*error_msg) { 95 | snprintf(*error_msg, 1024, "%s: %s", prefix, zip_strerror(zip)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /doc/ides.md: -------------------------------------------------------------------------------- 1 | ## IDEs 2 | 3 | ### Emacs 4 | 5 | Most Clojure developers using Emacs tend to use Cider. Cider needs the 6 | Clojure instance to be running `nRepl`, but Planck doesn't support 7 | that. Planck does instead implement the new Socket REPL capability, but Cider doesn't know how to interact with that. 8 | 9 | Luckily for us, [Rich Hickey](https://www.infoq.com/presentations/Simple-Made-Easy) 10 | [thinks Cider is too complex](https://batsov.com/articles/2014/12/04/introducing-inf-clojure-a-better-basic-clojure-repl-for-emacs/), 11 | so 12 | [Bozhidar Batsov](https://batsov.com) went ahead and created 13 | [inf-clojure](https://github.com/clojure-emacs/inf-clojure). 14 | 15 | #### Setup 16 | 17 | To set up `inf-clojure` to run with Planck, you can follow the 18 | instructions [here](https://github.com/clojure-emacs/inf-clojure) and 19 | add 20 | 21 | ``` 22 | (setq inf-clojure-generic-cmd "planck -d") 23 | ``` 24 | 25 | to your `.emacs` file, given `planck` is on your path. I would be 26 | careful doing 27 | 28 | ``` 29 | (add-hook 'clojure-mode-hook #'inf-clojure-minor-mode) 30 | ``` 31 | if you use Cider for your other Clojure work, and rather invoke 32 | `inf-clojure` by `M-x inf-clojure-minor-mode` when you're working with 33 | Planck. 34 | 35 | You can now evaluate code directly from your source-code buffer by 36 | pressing `C-x C-e` after the form you want to execute. 37 | 38 | ### Cursive 39 | 40 | It is possible to integrate Cursive with Planck using Planck's Socket REPL capability. To do this, set up a conventional ClojureScript project using, say Leiningen. Then add [Tubular](https://github.com/mfikes/tubular) as a dependency to the project via 41 | 42 | ``` 43 | [tubular "1.2.0"] 44 | ``` 45 | 46 | With this in place, first start up Planck in a regular terminal specifying the `src` directory of your project as Planck's `-c` classpath directive and use `-n` to have Planck listen on a port for Socket REPL sessions. For example: 47 | 48 | ``` 49 | $ planck -c src -n 7777 50 | ``` 51 | 52 | Within Cursive, add a REPL to the project and choose “Use clojure.main in normal JVM process”. Start up the REPL, and issue 53 | 54 | ``` 55 | (require 'tubular.core) 56 | (tubular.core/connect 7777) 57 | ``` 58 | 59 | This will piggyback a Socket REPL session in the Cursive Clojure REPL, and you will see the `cljs.user=>` prompt from Planck. Use the pulldown to switch Cursive's REPL type fro `clj` to `cljs`, and you should be good to go. In particular you can use Cursive's REPL menu option to load files into Planck, sync namespaces, and send forms to the Planck REPL. 60 | -------------------------------------------------------------------------------- /planck-cljs/test/planck/js_deps_test.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.js-deps-tests 2 | (:require [cljs.test :refer [deftest is testing are]] 3 | [planck.js-deps :as deps])) 4 | 5 | (deftest topo-sort-test 6 | ;; react.dom.server 7 | ;; / \ 8 | ;; / \ 9 | ;; \ react.dom 10 | ;; \ / 11 | ;; react 12 | (let [index '{react {:provides ["react"]} 13 | react.dom {:provides ["react.dom"] 14 | :requires ["react"]} 15 | react.dom.server {:provides ["react.dom.server"] 16 | :requires ["react" "react.dom"]}}] 17 | (is (= (->> (deps/topo-sort index 'react.dom.server)) 18 | '[react react.dom react.dom.server])) 19 | (is (= (->> (deps/topo-sort index 'react.dom)) 20 | '[react react.dom])) 21 | (is (= (->> (deps/topo-sort index 'react)) 22 | '[react])))) 23 | 24 | (deftest add-js-libs-test 25 | (let [js-libs [{:provides ["react"]} 26 | {:provides ["react.dom"] 27 | :requires ["react"]} 28 | {:provides ["react.dom.server"] 29 | :requires ["react" "react.dom"]}]] 30 | (is (= (deps/add-js-libs {} js-libs) 31 | '{react {:provides ["react"]} 32 | react.dom {:provides ["react.dom"] 33 | :requires ["react"]} 34 | react.dom.server {:provides ["react.dom.server"] 35 | :requires ["react" "react.dom"]}})))) 36 | 37 | (deftest js-libs-to-load-test 38 | (with-redefs [^:private-var-access-nowarn deps/js-lib-index 39 | (volatile! '{react {:provides ["react"] 40 | :file "react file"} 41 | react.dom {:provides ["react.dom"] 42 | :requires ["react"] 43 | :file "react.dom file"} 44 | react.dom.server {:provides ["react.dom.server"] 45 | :requires ["react" "react.dom"] 46 | :file "react.dom.server file"}})] 47 | (is (= (map :file (deps/js-libs-to-load 'react.dom)) 48 | ["react file" "react.dom file"])) 49 | (is (= (map :file (deps/js-libs-to-load 'react.dom.server)) 50 | ["react file" "react.dom file" "react.dom.server file"])) 51 | (is (= (map :file (deps/js-libs-to-load 'react)) 52 | ["react file"])))) -------------------------------------------------------------------------------- /planck-cljs/src/planck/closure.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.closure 2 | "Provides access to the Closure compiler" 3 | (:require 4 | [cljs.source-map :as sm])) 5 | 6 | (defn- call-compiler 7 | [name source sm-data optimizations verbose] 8 | (when-not (exists? js/compile) 9 | (js/AMBLY_IMPORT_SCRIPT "jscomp.js")) 10 | (when verbose 11 | (println "Applying" optimizations "Closure optimizations to" name)) 12 | (try 13 | (js/compile #js {:jsCode #js [#js {:src source}] 14 | :compilationLevel (case optimizations 15 | :simple "SIMPLE" 16 | :whitespace "WHITESPACE_ONLY") 17 | :languageIn "ECMASCRIPT5" 18 | :languageOut "ECMASCRIPT3" 19 | :processClosurePrimitives false 20 | :createSourceMap (some? sm-data) 21 | :applyInputSourceMaps false}) 22 | (catch :default e 23 | (throw (ex-info "Internal error running Closure compiler" 24 | {:name name, :optimizations optimizations} e))))) 25 | 26 | (defn- check-compilation-results 27 | [name optimizations results] 28 | (when-not (empty? (.-warnings results)) 29 | (println "Closure compilation warnings" (.-warnings results))) 30 | (when-not (empty? (.-errors results)) 31 | (println "Closure compilation errors" (.-errors results)) 32 | (throw (ex-info "Closure compilation errors" {:name name 33 | :optimizations optimizations 34 | :errors (.-errors results)}))) 35 | results) 36 | 37 | (defn- extract-results 38 | [source sm-data results] 39 | (if (.-compiledCode results) 40 | (merge {:source (.-compiledCode results)} 41 | (when (some? sm-data) 42 | {:source-map (->> results 43 | .-sourceMap 44 | (.parse js/JSON) 45 | sm/decode-reverse 46 | vals 47 | first 48 | (sm/merge-source-maps (:source-map sm-data)) 49 | sm/invert-reverse-map)})) 50 | {:source source})) 51 | 52 | (defn compile 53 | "Uses Closure to compile JavaScript source. If :sm-data is supplied, a 54 | composed :source-map will calculated and be returned in the result." 55 | [{:keys [name source sm-data optimizations verbose] 56 | :or {optimizations :simple}}] 57 | (->> (call-compiler name source sm-data optimizations verbose) 58 | (check-compilation-results name optimizations) 59 | (extract-results source sm-data))) 60 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/socket.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.socket 2 | "Planck socket functionality." 3 | (:require 4 | [cljs.spec.alpha :as s])) 5 | 6 | (s/def ::host string?) 7 | (s/def ::port integer?) 8 | (s/def ::data string?) ; Maybe also byte arrays in the future? 9 | (s/def ::socket integer?) 10 | (s/def ::data-handler ifn?) 11 | (s/def ::accept-handler ifn?) 12 | (s/def ::opts (s/nilable map?)) 13 | 14 | (defn connect 15 | "Connects a TCP socket to a remote host/port. The connected socket reference 16 | is returned. Data can be written to the socket using [[write]] and the socket 17 | can be closed using [[close]]. 18 | 19 | A data-handler argument must be supplied, which is a function that accepts a 20 | socket reference and a nillable data value. This data handler will be called 21 | when data arrives on the socket. When the socket is closed the data handler 22 | will be called with a nil data value." 23 | ([host port data-handler] 24 | (connect host port data-handler nil)) 25 | ([host port data-handler opts] 26 | (js/PLANCK_SOCKET_CONNECT host port data-handler))) 27 | 28 | (s/fdef connect 29 | :args (s/cat :host ::host :port ::port :data-handler ::data-handler :opts (s/? ::opts)) 30 | :ret ::socket) 31 | 32 | (defn write 33 | "Writes data to a socket." 34 | ([socket data] 35 | (write socket data nil)) 36 | ([socket data opts] 37 | (js/PLANCK_SOCKET_WRITE socket data))) 38 | 39 | (s/fdef write 40 | :args (s/cat :socket ::socket :data ::data :opts (s/? ::opts))) 41 | 42 | (defn close 43 | "Closes a socket." 44 | ([socket] 45 | (close socket nil)) 46 | ([socket opts] 47 | (js/PLANCK_SOCKET_CLOSE socket))) 48 | 49 | (s/fdef close 50 | :args (s/cat :socket ::socket :opts (s/? ::opts)) 51 | :ret nil?) 52 | 53 | (defn listen 54 | "Opens a server socket, listening for inbound connections. The port to 55 | listen on must be specified, along with an accept-handler. 56 | 57 | The accept-handler should be a function that accepts a socket reference and 58 | returns a data handler. 59 | 60 | The data handler is a function that accepts a socket reference and a 61 | nillable data value. This data handler will be called when data arrives on 62 | the socket. When the socket is closed the data handler will be called with a 63 | nil data value. 64 | 65 | For example, an echo server could be written in this way: 66 | 67 | (listen 55555 68 | (fn [socket] 69 | (fn [socket data] 70 | (when data 71 | (write socket data)))))" 72 | ([port accept-handler] 73 | (listen port accept-handler nil)) 74 | ([port accept-handler opts] 75 | (js/PLANCK_SOCKET_LISTEN port accept-handler))) 76 | 77 | (s/fdef listen 78 | :args (s/cat :socket ::socket :accept-handler ::accept-handler :opts (s/? ::opts)) 79 | :ret nil?) 80 | -------------------------------------------------------------------------------- /doc/testing.md: -------------------------------------------------------------------------------- 1 | ## Testing 2 | 3 | It is possible to write and execute unit tests using Planck. 4 | 5 | Let's say you have a namespace with a function you'd like to test. 6 | 7 | ```clojure 8 | (ns foo.core) 9 | 10 | (defn square 11 | [x] 12 | (+ x x)) 13 | ``` 14 | 15 | You can test `foo.core` by writing a test namespace: 16 | 17 | ```clojure 18 | (ns foo.core-test 19 | (:require [cljs.test :refer-macros [deftest is]] 20 | [foo.core])) 21 | 22 | (deftest test-square 23 | (is (= 0 (foo.core/square 0))) 24 | (is (= 9 (foo.core/square 3)))) 25 | ``` 26 | 27 | Then you can run the unit tests using `run-tests`: 28 | 29 | ```clojure-repl 30 | cljs.user=> (cljs.test/run-tests 'foo.core-test) 31 | 32 | Testing foo.core-test 33 | 34 | FAIL in (test-square) (:5:1) 35 | expected: (= 9 (foo.core/square 3)) 36 | actual: (not (= 9 6)) 37 | 38 | Ran 1 tests containing 2 assertions. 39 | 1 failures, 0 errors. 40 | nil 41 | ``` 42 | 43 | If you fix the definition of `square` to make use of `*` instead of `+`, then you can run the tests again and see thing they pass: 44 | 45 | ```clojure-repl 46 | cljs.user=> (cljs.test/run-tests 'foo.core-test) 47 | 48 | Testing foo.core-test 49 | 50 | Ran 1 tests containing 2 assertions. 51 | 0 failures, 0 errors. 52 | nil 53 | ``` 54 | 55 | ### Custom Asserts 56 | 57 | The `cljs.test` library provides a mechanism for writing custom asserts that can be used with the `is` macro—in the form of an `assert-expr` `defmulti`. 58 | 59 | To define your own assert, simply provide a `defmethod` for `cljs.test$macros/assert-expr`. Here's an example: 60 | 61 | If you evaluate `(is (char? nil))` you will get a cryptic error report: 62 | 63 | ``` 64 | ERROR in () (isUnicodeChar@file:269:12) 65 | expected: (char? nil) 66 | actual: #object[TypeError TypeError: null is not an object (evaluating 'ch.length')] 67 | ``` 68 | 69 | You can define a custom assert for this situation: 70 | 71 | ```clojure 72 | (defmethod cljs.test$macros/assert-expr 'char? 73 | [menv msg form] 74 | (let [arg (second form) 75 | result (and (not (nil? arg)) 76 | (char? arg))] 77 | `(do 78 | (if ~result 79 | (cljs.test/do-report 80 | {:type :pass 81 | :message ~msg 82 | :expected '~form 83 | :actual (list '~'char? ~arg)}) 84 | (cljs.test/do-report 85 | {:type :fail 86 | :message ~msg 87 | :expected '~form 88 | :actual (list '~'not 89 | (list '~'char? ~arg))})) 90 | ~result))) 91 | ``` 92 | 93 | With this, `(is (char? nil))` yields: 94 | 95 | ``` 96 | FAIL in () (eval@[native code]:NaN:NaN) 97 | expected: (char? nil) 98 | actual: (not (char? nil)) 99 | ``` 100 | -------------------------------------------------------------------------------- /script/get-closure-library: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ "${VERBOSE_BUILD:-0}" == "1" ]; then 6 | set -x 7 | fi 8 | 9 | output=planck-cljs/src/planck/bundle/gcl.cljs 10 | 11 | if [ ! -f $output ]; then 12 | 13 | echo "Fetching Google Closure Library..." 14 | 15 | if ! [ -d planck-cljs/lib/closure ]; then 16 | mkdir -p planck-cljs/lib/closure 17 | cd planck-cljs/lib/closure 18 | curl --retry 3 -LO -s https://repo1.maven.org/maven2/org/clojure/google-closure-library/$GCL_RELEASE/google-closure-library-$GCL_RELEASE.jar || { echo "Download failed."; exit 1; } 19 | jar xf google-closure-library-$GCL_RELEASE.jar 20 | rm google-closure-library-$GCL_RELEASE.jar 21 | cd .. 22 | mkdir -p third_party/closure 23 | cd third_party/closure 24 | curl --retry 3 -O -s https://repo1.maven.org/maven2/org/clojure/google-closure-library-third-party/$GCL_RELEASE/google-closure-library-third-party-$GCL_RELEASE.jar || { echo "Download failed."; exit 1; } 25 | jar xf google-closure-library-third-party-$GCL_RELEASE.jar 26 | rm google-closure-library-third-party-$GCL_RELEASE.jar 27 | cd ../.. 28 | cd ../.. 29 | fi 30 | 31 | mkdir -p planck-cljs/src/planck/bundle 32 | 33 | cat < $output 34 | (ns planck.bundle.gcl 35 | "Require the namespaces that make up the Google Closure Library." 36 | (:require 37 | EOF 38 | 39 | cd planck-cljs/lib/closure/goog 40 | for ns in `find . -path './debug' -prune \ 41 | -or -path './demos' -prune \ 42 | -or -path './editor' -prune \ 43 | -or -path './events' -prune \ 44 | -or -path './fx' -prune \ 45 | -or -path './graphics' -prune \ 46 | -or -path './labs' -prune \ 47 | -or -path './messaging/testdata' -prune \ 48 | -or -path './module/testdata' -prune \ 49 | -or -path './net/testdata' -prune \ 50 | -or -path './testing' -prune \ 51 | -or -path './ui' -prune \ 52 | -or -name '*.js' \ 53 | -not -name '*_test.js' \ 54 | -exec sed -n "s/^goog\.provide('\(goog\...*\)');$/\1/p" {} \; | \ 55 | sort -u` 56 | do 57 | if [[ ! "$ns" =~ i18n.*_[A-Za-z][A-Za-z] ]] 58 | then 59 | cat <> ../../../../$output 60 | [$ns] 61 | EOF 62 | fi 63 | done 64 | cd ../../../.. 65 | 66 | cd planck-cljs/lib/third_party/closure/goog 67 | for ns in `find . -path './svgpan' -prune \ 68 | -or -name '*.js' \ 69 | -not -name '*_test.js' \ 70 | -exec sed -n "s/^goog\.provide('\(goog\...*\)');$/\1/p" {} \; | \ 71 | sort -u` 72 | do 73 | cat <> ../../../../../$output 74 | [$ns] 75 | EOF 76 | done 77 | cd ../../../../.. 78 | 79 | cat <> $output 80 | )) 81 | EOF 82 | 83 | fi 84 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/socket/alpha.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.socket.alpha 2 | "Planck socket functionality." 3 | (:require 4 | [cljs.spec.alpha :as s] 5 | [planck.repl :as repl])) 6 | 7 | (s/def ::host string?) 8 | (s/def ::port integer?) 9 | (s/def ::data string?) ; Maybe also byte arrays in the future? 10 | (s/def ::socket integer?) 11 | (s/def ::data-handler ifn?) 12 | (s/def ::accept-handler ifn?) 13 | (s/def ::opts (s/nilable map?)) 14 | 15 | (defn ^:deprecated connect 16 | "Connects a TCP socket to a remote host/port. The connected socket reference 17 | is returned. Data can be written to the socket using `write` and the socket 18 | can be closed using `close`. 19 | 20 | A data-handler argument must be supplied, which is a function that accepts a 21 | socket reference and a nillable data value. This data handler will be called 22 | when data arrives on the socket. When the socket is closed the data handler 23 | will be called with a nil data value." 24 | ([host port data-handler] 25 | ^:deprecation-nowarn 26 | (connect host port data-handler nil)) 27 | ([host port data-handler opts] 28 | (js/PLANCK_SOCKET_CONNECT host port data-handler))) 29 | 30 | (s/fdef connect 31 | :args (s/cat :host ::host :port ::port :data-handler ::data-handler :opts (s/? ::opts)) 32 | :ret ::socket) 33 | 34 | (defn ^:deprecated write 35 | "Writes data to a socket." 36 | ([socket data] 37 | ^:deprecation-nowarn 38 | (write socket data nil)) 39 | ([socket data opts] 40 | (js/PLANCK_SOCKET_WRITE socket data))) 41 | 42 | (s/fdef write 43 | :args (s/cat :socket ::socket :data ::data :opts (s/? ::opts))) 44 | 45 | (defn ^:deprecated close 46 | "Closes a socket." 47 | ([socket] 48 | ^:deprecation-nowarn 49 | (close socket nil)) 50 | ([socket opts] 51 | (js/PLANCK_SOCKET_CLOSE socket))) 52 | 53 | (s/fdef close 54 | :args (s/cat :socket ::socket :opts (s/? ::opts)) 55 | :ret nil?) 56 | 57 | (defn ^:deprecated listen 58 | "Opens a server socket, listening for inbound connections. The port to 59 | listen on must be specified, along with an accept-handler. 60 | 61 | The accept-handler should be a function that accepts a socket reference and 62 | returns a data handler. 63 | 64 | The data handler is a function that accepts a socket reference and a 65 | nillable data value. This data handler will be called when data arrives on 66 | the socket. When the socket is closed the data handler will be called with a 67 | nil data value. 68 | 69 | For example, an echo server could be written in this way: 70 | 71 | (listen 55555 72 | (fn [socket] 73 | (fn [socket data] 74 | (when data 75 | (write socket data)))))" 76 | ([port accept-handler] 77 | ^:deprecation-nowarn 78 | (listen port accept-handler nil)) 79 | ([port accept-handler opts] 80 | (js/PLANCK_SOCKET_LISTEN port accept-handler))) 81 | 82 | (s/fdef listen 83 | :args (s/cat :socket ::socket :accept-handler ::accept-handler :opts (s/? ::opts)) 84 | :ret nil?) 85 | -------------------------------------------------------------------------------- /planck-c/theme.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static char *font_colors[] = 7 | {"no-font", "", 8 | "black-font", "\x1b[30m", 9 | "red-font", "\x1b[31m", 10 | "green-font", "\x1b[32m", 11 | "yellow-font", "\x1b[33m", 12 | "blue-font", "\x1b[34m", 13 | "magenta-font", "\x1b[35m", 14 | "cyan-font", "\x1b[36m", 15 | "white-font", "\x1b[37m", 16 | "black-bold-font", "\x1b[40m", 17 | "red-bold-font", "\x1b[41m", 18 | "green-bold-font", "\x1b[42m", 19 | "yellow-bold-font", "\x1b[43m", 20 | "blue-bold-font", "\x1b[44m", 21 | "magenta-bold-font", "\x1b[45m", 22 | "cyan-bold-font", "\x1b[46m", 23 | "white-bold-font", "\x1b[47m"}; 24 | 25 | static char *prompt_fonts[] = 26 | {"plain", "no-font", 27 | "light", "cyan-font", 28 | "dark", "blue-font"}; 29 | 30 | const char *color_for_font(const char *font) { 31 | 32 | int i; 33 | for (i = 0; i < sizeof(font_colors) / sizeof(font_colors[0]); i += 2) { 34 | if (strcmp(font, font_colors[i]) == 0) { 35 | return font_colors[i + 1]; 36 | } 37 | } 38 | 39 | return NULL; 40 | } 41 | 42 | const char *prompt_font_for_theme(const char *theme) { 43 | 44 | int i; 45 | for (i = 0; i < sizeof(prompt_fonts) / sizeof(prompt_fonts[0]); i += 2) { 46 | if (strcmp(theme, prompt_fonts[i]) == 0) { 47 | return prompt_fonts[i + 1]; 48 | } 49 | } 50 | 51 | return NULL; 52 | } 53 | 54 | const char *default_theme_for_terminal() { 55 | 56 | // Check NO_COLOR env var 57 | 58 | char *no_color = getenv("NO_COLOR"); 59 | if (no_color) { 60 | return "plain"; 61 | } 62 | 63 | // Check COLORFGBG env var 64 | 65 | char *color_fg_bg = getenv("COLORFGBG"); 66 | if (color_fg_bg) { 67 | strtok(color_fg_bg, ";"); 68 | char *bg = strtok(NULL, ";"); 69 | if (bg && strcmp(bg, "0") == 0) { 70 | return "dark"; 71 | } 72 | } 73 | 74 | return "light"; 75 | } 76 | 77 | const char *prompt_ansi_code_for_theme(const char *theme) { 78 | 79 | const char *font = prompt_font_for_theme(theme); 80 | 81 | return font ? color_for_font(font) : NULL; 82 | } 83 | 84 | bool check_theme(const char *theme) { 85 | 86 | if (prompt_font_for_theme(theme)) { 87 | return true; 88 | } 89 | 90 | int err = fprintf(stderr, "Unsupported theme: %s\n", theme); 91 | if (err == -1) return false; 92 | 93 | err = fprintf(stderr, "Supported themes:\n"); 94 | if (err == -1) return false; 95 | 96 | int i; 97 | for (i = 0; i < sizeof(prompt_fonts) / sizeof(prompt_fonts[0]); i += 2) { 98 | err = fprintf(stderr, " %s\n", prompt_fonts[i]); 99 | if (err == -1) return false; 100 | } 101 | 102 | return false; 103 | } 104 | 105 | -------------------------------------------------------------------------------- /doc/planck-namespaces.md: -------------------------------------------------------------------------------- 1 | ## Planck Namespaces 2 | 3 | 4 | 5 | In order to make Planck more useful for doing actual work and interacting with your computer and the outside world, some native I/O facilities have been added to the JavaScriptCore instance running in Planck and these have been exposed via a few namespaces. To make things easier to use, the functions in these namespaces adhere fairly closely to existing Clojure / ClojureScript analogs. 6 | 7 | The code for these namespaces is included directly _in the Planck binary_, so they are always available—you just need to `require` them. 8 | 9 | These namespaces comprise 10 | * `planck.core` 11 | * `planck.environ` 12 | * `planck.http` 13 | * `planck.io` 14 | * `planck.repl` 15 | * `planck.shell` 16 | 17 | To explore these namespaces, you can evaluate `(dir planck.core)`, for example, to see the symbols in `planck.core`, and then use the `doc` macro to see the docs for any of the symbols. 18 | 19 | ### planck.core 20 | 21 | This namespace primarily collects functions that are part of Clojure's `clojure.core` but are not part of ClojureScript. This includes basic I/O capabilities like `slurp`, `spit` and `read-line` as well as functions useful for console scripting like `file-seq` (directory listing), `read-password` (password input) and `exit` (exit values). 22 | 23 | The I/O facilities are expressed in protocols defined in `planck.core` modeled after those in Clojure, like `IReader`, `IOutputStream`, _etc._, and these capabilities cooperate with facilities defined in `planck.io`. 24 | 25 | The `planck.core` namespace also defines dynamic functions like `resolve`, `ns-resolve`, and `intern`. Some dynamic vars of interest like `*in*` and `*out*` are defined here as well. 26 | 27 | ### planck.environ 28 | 29 | This namespace provided access to environment variables, modeled after [Environ](https://github.com/weavejester/environ). For example 30 | 31 | ``` 32 | (:home planck.environ/env) 33 | ``` 34 | 35 | will access the `HOME` environment variable. 36 | 37 | ### planck.http 38 | 39 | This namespace provides facilities for interacting with HTTP servers. For example: 40 | 41 | ``` 42 | (planck.http/get "https://planck-repl.org") 43 | ``` 44 | 45 | will fetch the main page of the Planck website, returning the status code, headers, and body in a map structure. 46 | 47 | ### planck.io 48 | 49 | This namespace defines a lot of the `IOFactory` machinery, imitating `clojure.java.io`. File system facilities like `file`, `delete-file`, and `file-attributes` are also made available. 50 | 51 | ### planck.repl 52 | 53 | This namespace includes a few macros that are useful when working at the REPL, such as `doc`, `dir`, `source`, _etc_. 54 | 55 | ### planck.shell 56 | 57 | This namespace imitates `clojure.shell`, and defining the `sh` function and `with-sh-dir` / `with-sh-env` macros that can be used to execute external command-line functions. 58 | 59 | With this escape hatch, you can do nearly anything: move files to remote hosts using `scp`, _etc._ 60 | -------------------------------------------------------------------------------- /planck-c/file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "unicode/ustdio.h" 5 | #include "file.h" 6 | 7 | descriptor_t ufile_to_descriptor(UFILE *ufile) { 8 | return (descriptor_t) ufile; 9 | } 10 | 11 | UFILE *descriptor_to_ufile(descriptor_t descriptor) { 12 | return (UFILE *) descriptor; 13 | } 14 | 15 | descriptor_t ufile_open(const char *path, const char *encoding, const char *mode) { 16 | return ufile_to_descriptor(u_fopen(path, mode, NULL, encoding)); 17 | } 18 | 19 | descriptor_t ufile_open_read(const char *path, const char *encoding) { 20 | return ufile_open(path, encoding, "r"); 21 | } 22 | 23 | descriptor_t ufile_open_write(const char *path, bool append, const char *encoding) { 24 | return ufile_open(path, encoding, (append ? "a" : "w")); 25 | } 26 | 27 | JSStringRef ufile_read(descriptor_t descriptor) { 28 | UFILE *ufile = descriptor_to_ufile(descriptor); 29 | JSStringRef rv = NULL; 30 | void *buffer = malloc(sizeof(uint16_t) * 1024); 31 | int32_t read = u_file_read(buffer, 1024, ufile); 32 | /* If we've read to the end of the file, clear the EOF indicator 33 | * so that subsequent read calls will try again. */ 34 | if (u_feof(ufile)) { 35 | clearerr(u_fgetfile(ufile)); 36 | } 37 | if (read > 0) { 38 | rv = JSStringCreateWithCharacters(buffer, (size_t) read); 39 | } 40 | free(buffer); 41 | return rv; 42 | } 43 | 44 | void ufile_write(descriptor_t descriptor, JSStringRef text) { 45 | UFILE *ufile = descriptor_to_ufile(descriptor); 46 | u_file_write(JSStringGetCharactersPtr(text), (uint32_t) JSStringGetLength(text), ufile); 47 | } 48 | 49 | void ufile_flush(descriptor_t descriptor) { 50 | UFILE *ufile = descriptor_to_ufile(descriptor); 51 | u_fflush(ufile); 52 | } 53 | 54 | void ufile_close(descriptor_t descriptor) { 55 | UFILE *ufile = descriptor_to_ufile(descriptor); 56 | u_fclose(ufile); 57 | } 58 | 59 | descriptor_t file_to_descriptor(FILE *file) { 60 | return (descriptor_t) file; 61 | } 62 | 63 | FILE *descriptor_to_file(descriptor_t descriptor) { 64 | return (FILE *) descriptor; 65 | } 66 | 67 | descriptor_t file_open(const char *path, const char *mode) { 68 | return file_to_descriptor(fopen(path, mode)); 69 | } 70 | 71 | descriptor_t file_open_read(const char *path) { 72 | return file_open(path, "r"); 73 | } 74 | 75 | descriptor_t file_open_write(const char *path, bool append) { 76 | return file_open(path, (append ? "a" : "w")); 77 | } 78 | 79 | size_t file_read(descriptor_t descriptor, size_t buf_size, uint8_t *buf) { 80 | FILE *file = descriptor_to_file(descriptor); 81 | return fread(buf, sizeof(uint8_t), buf_size, file); 82 | } 83 | 84 | void file_write(descriptor_t descriptor, size_t buf_size, uint8_t *buf) { 85 | FILE *file = descriptor_to_file(descriptor); 86 | fwrite(buf, sizeof(uint8_t), buf_size, file); 87 | } 88 | 89 | void file_flush(descriptor_t descriptor) { 90 | FILE *file = descriptor_to_file(descriptor); 91 | fflush(file); 92 | } 93 | 94 | void file_close(descriptor_t descriptor) { 95 | FILE *file = descriptor_to_file(descriptor); 96 | fclose(file); 97 | } 98 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/js_deps.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.js-deps 2 | (:require 3 | [cljs.tools.reader :as r] 4 | [clojure.string :as string])) 5 | 6 | (defonce js-lib-index (volatile! {})) 7 | 8 | (defn add-js-lib 9 | "Adds a js lib to the index." 10 | [index {:keys [provides] :as js-lib}] 11 | (reduce (fn [index provided-lib] 12 | (assoc index (symbol provided-lib) js-lib)) 13 | index 14 | provides)) 15 | 16 | (defn add-js-libs 17 | "Adds multiple js libs to the index." 18 | [index js-libs] 19 | (reduce add-js-lib index js-libs)) 20 | 21 | (defn parse-closure-ns 22 | "Parses a js source file which uses the closure module system." 23 | [source] 24 | (->> source 25 | (string/split-lines) 26 | (mapcat #(string/split % #";")) 27 | (map string/trim) 28 | (take-while #(not (re-matches #".*=[\s]*function\(.*\)[\s]*[{].*" %))) 29 | (keep #(re-matches #".*goog\.(provide|require)\(['\"](.*)['\"]\)" %)) 30 | (map rest) 31 | (reduce (fn [m ns] 32 | (let [munged-ns (string/replace (last ns) "_" "-")] 33 | (update m (if (= (first ns) "require") 34 | :requires 35 | :provides) 36 | conj munged-ns))) 37 | {:requires [] :provides []}))) 38 | 39 | (defn file-seq 40 | "A tree seq on files" 41 | [dir] 42 | (tree-seq 43 | (fn [f] (js/PLANCK_IS_DIRECTORY f)) 44 | (fn [d] (vec (js/PLANCK_LIST_FILES d))) 45 | dir)) 46 | 47 | (defn parse-libs 48 | "Converts a closure lib path into a list of module descriptors." 49 | [lib] 50 | (->> lib 51 | (file-seq) 52 | (filter #(string/ends-with? % ".js")) 53 | (map (fn [file] 54 | (let [source (first (js/PLANCK_READ_FILE file))] 55 | (when-not source 56 | (throw (ex-info "The specified closure library does not exist" {:path file}))) 57 | (assoc (parse-closure-ns source) :file file)))))) 58 | 59 | (defn- add-libs 60 | [index {:keys [libs foreign-libs]}] 61 | (add-js-libs index (concat foreign-libs (mapcat parse-libs libs)))) 62 | 63 | (defn index-js-libs 64 | "Indexes all js foreign and closure libs from each deps.cljs on the classpath." 65 | [] 66 | (vswap! js-lib-index 67 | (fn [index] 68 | (reduce (fn [index [_ deps-cljs-str]] 69 | (add-libs index (r/read-string deps-cljs-str))) 70 | index 71 | (js/PLANCK_LOAD_DEPS_CLJS_FILES))))) 72 | 73 | (defn index-opts 74 | [opts] 75 | (vswap! js-lib-index add-libs opts)) 76 | 77 | (defn js-lib? 78 | "Returns true if the argument is a js lib." 79 | [dep] 80 | (contains? @js-lib-index dep)) 81 | 82 | (defn topo-sort 83 | "Returns a list of dependencies in the topological order." 84 | [index dep] 85 | (loop [ret '() 86 | s #{dep}] 87 | (if (empty? s) 88 | (distinct ret) 89 | (let [dep (first s) 90 | requires (map symbol (:requires (get index dep)))] 91 | (recur (conj ret dep) (into (set (rest s)) requires)))))) 92 | 93 | (defn js-libs-to-load 94 | "Returns a list of dependencies to load for the given lib." 95 | [lib] 96 | (let [index @js-lib-index] 97 | (map index (topo-sort index lib)))) 98 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/pprint/width_adjust.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.pprint.width-adjust 2 | "Adjust pretty-print width for trailing delimiters." 3 | (:require 4 | [clojure.string :as string] 5 | [fipp.engine :refer [annotate-begins annotate-rights format-nodes serialize]] 6 | [planck.themes])) 7 | 8 | (def plain (planck.themes/get-theme :plain)) 9 | 10 | (defn pprint-document [print-fn document options] 11 | (->> (serialize document) 12 | (eduction 13 | annotate-rights 14 | (annotate-begins options) 15 | (format-nodes options)) 16 | (run! print-fn))) 17 | 18 | (defn counting-print 19 | [print-fn max-prints] 20 | (let [counter (atom 0)] 21 | (fn [x] 22 | (print-fn x) 23 | (when (== max-prints (swap! counter inc)) 24 | (throw (ex-info "" {::count-reached true})))))) 25 | 26 | (defn count-reached? 27 | [e] 28 | (::count-reached (ex-data e))) 29 | 30 | (defn generate-sample 31 | [pprint x opts width max-prints] 32 | (let [sb (js/goog.string.StringBuffer.) 33 | print-to-sb (fn [x] 34 | (.append sb x))] 35 | (try 36 | (pprint x (assoc opts :width width 37 | :pprint-document (partial pprint-document (counting-print print-to-sb max-prints)))) 38 | (catch :default e 39 | (when-not (count-reached? e) 40 | (throw e)))) 41 | (str sb))) 42 | 43 | (defn text-width 44 | [text] 45 | (->> text 46 | string/split-lines 47 | (map count) 48 | (apply max))) 49 | 50 | (defn bisect 51 | [lower upper good?] 52 | (if (or (good? upper) 53 | (<= upper lower) 54 | (not (good? lower))) 55 | upper 56 | (loop [lower lower 57 | upper upper] 58 | (let [mid (quot (+ lower upper) 2)] 59 | (if (or (== mid upper) 60 | (== mid lower)) 61 | lower 62 | (if (good? mid) 63 | (recur mid upper) 64 | (recur lower mid))))))) 65 | 66 | (defn force-eval 67 | [pprint x opts max-prints] 68 | (try 69 | (pprint x (assoc opts :pprint-document (partial pprint-document (counting-print identity max-prints)))) 70 | false 71 | (catch :default e 72 | (if (count-reached? e) 73 | true 74 | (throw e))))) 75 | 76 | (defn adjusted-with 77 | [pprint x opts] 78 | (let [plain-opts (assoc opts :theme plain) 79 | max-prints 1024] 80 | ;; We first force the evaluation of up to max-prints to ensure any print 81 | ;; side effects occur. We give up if max-prints is reached. 82 | (if (force-eval pprint x plain-opts max-prints) 83 | (:width opts) 84 | (let [desired-width (:width opts) 85 | lower 20 86 | upper desired-width 87 | sample-width (fn [trial-width] 88 | (text-width (generate-sample pprint x plain-opts trial-width max-prints))) 89 | fits? (fn [trial-width] 90 | (<= (sample-width trial-width) desired-width))] 91 | (bisect lower upper fits?))))) 92 | 93 | (defn wrap 94 | [pprint] 95 | (fn wrapped 96 | ([x] (wrapped x nil)) 97 | ([x opts] (pprint x (assoc opts :width (adjusted-with pprint x opts)))))) 98 | -------------------------------------------------------------------------------- /doc/running.md: -------------------------------------------------------------------------------- 1 | ## Running 2 | 3 | 4 | 5 | You launch Planck by typing `planck` in a terminal. 6 | 7 | You can also launch Planck using the `plk` script, which integrates with the [`clojure`](https://clojure.org/guides/getting_started) CLI tool to add support for `deps.edn` and classpath-affecting options such as `-Aalias`. 8 | 9 | If you'd like to get help on the command line arguments to Planck, do 10 | 11 | ``` 12 | planck -h 13 | ``` 14 | 15 | or 16 | 17 | ``` 18 | plk -h 19 | ``` 20 | 21 | The command line arguments to Planck are heavily modeled after the ones available for the Clojure REPL. In particular, you can provide zero or more _init-opt_ arguments, followed by an optional _main-opt_ argument, followed by zero or more _arg_ arguments. Planck also accepts _long arguments_ (preceeded by two dashes), just like the Clojure REPL. 22 | 23 | ### Auto-loaded user code 24 | 25 | When a Planck starts, it automatically loads any `user.cljs` or `user.cljc` 26 | file present on your classpath. This is an ideal location to place code 27 | that is useful for development time. 28 | 29 | The file may contain an `ns` form to load required namespaces or to establish 30 | the namespace for any `def` forms that appear in the file. If no namespace 31 | is specified, `cljs.user` is assumed. 32 | 33 | 34 | ### Compile Opts EDN 35 | 36 | Many of the command line arguments may also supplied via **edn**, passed via `-co` / `--compile-opts`. Any opts passed via `-co` / `--compile-opts` are merged onto any base opts specified directly by command-line flags. 37 | 38 | Also, note that it is possible to configure certain behaviors via `-co` / `--compile-opts` where there doesn't exist a direct command line flag. 39 | 40 | Compile opts **edn** may be specified directly on the command line as in 41 | 42 | ``` 43 | plk --compile-opts '{:closure-defines {foo.core/x "bar"}}' -r 44 | ``` 45 | 46 | or by specifying a file, where an optional leading `@` means that the file should be read from the classpath as in: 47 | 48 | ``` 49 | plk --compile-opts @/my-compile-opts.edn -r 50 | ``` 51 | 52 | Options that may be configured via `-co` / `--compile-opts` comprise: 53 | 54 | - [:checked-arrays](https://clojurescript.org/reference/compiler-options#checked-arrays) 55 | - [:def-emits-var](https://clojurescript.org/reference/repl-options#def-emits-var) 56 | - [:elide-asserts](https://clojurescript.org/reference/compiler-options#elide-asserts) 57 | - [:fn-invoke-direct](https://clojurescript.org/reference/compiler-options#fn-invoke-direct) 58 | - [:foreign-libs](https://clojurescript.org/reference/compiler-options#foreign-libs) 59 | - [:libs](https://clojurescript.org/reference/compiler-options#libs) 60 | - [:optimizations](https://clojurescript.org/reference/compiler-options#optimizations) 61 | - [:repl-requires](https://clojurescript.org/reference/repl-options#repl-requires) 62 | - [:source-map](https://clojurescript.org/reference/compiler-options#source-map) 63 | - [:spec-skip-macros](https://clojurescript.org/reference/compiler-options#spec-skip-macros) 64 | - [:static-fns](https://clojurescript.org/reference/compiler-options#static-fns) 65 | - [:verbose](https://clojurescript.org/reference/compiler-options#verbose) 66 | - [:warnings](https://clojurescript.org/reference/compiler-options#warnings) 67 | - [:warn-on-undeclared](https://clojurescript.org/reference/repl-options#warn-on-undeclared) 68 | -------------------------------------------------------------------------------- /planck-cljs/test/planck/shell_test.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.shell-test 2 | (:require 3 | [clojure.string :as string] 4 | [clojure.test :refer [deftest is]] 5 | [planck.core] 6 | [planck.io :as io] 7 | [planck.shell :include-macros true])) 8 | 9 | (deftest shell 10 | (is (= #{[:err ""] [:exit 0] [:out "hello\n"]} 11 | (into (sorted-set) (planck.shell/sh "echo" "hello"))))) 12 | 13 | (deftest shell-throws 14 | (is (thrown-with-msg? js/Error 15 | #"Launch path \"bogus\" not accessible." 16 | (planck.shell/sh "bogus")))) 17 | 18 | (deftest capture-exit-value 19 | (is (= 0 (:exit (planck.shell/sh "sh" "-c" "exit 0")))) 20 | (is (= 1 (:exit (planck.shell/sh "sh" "-c" "exit 1")))) 21 | (is (= 17 (:exit (planck.shell/sh "sh" "-c" "exit 17"))))) 22 | 23 | (deftest with-sh-dir-test 24 | (is (string/ends-with? 25 | (:out (planck.shell/with-sh-dir "script" 26 | (planck.shell/sh "pwd"))) 27 | "script\n")) 28 | (is (string/ends-with? 29 | (:out (planck.shell/with-sh-dir (planck.io/file "script") 30 | (planck.shell/sh "pwd"))) 31 | "script\n")) 32 | (let [rv (planck.shell/with-sh-dir "bogus" 33 | (planck.shell/sh "pwd"))] 34 | (is (not= 0 (:exit rv))))) 35 | 36 | (deftest specify-dir-test 37 | (is (string/ends-with? 38 | (:out (planck.shell/sh "pwd" :dir "script")) 39 | "script\n")) 40 | (is (string/ends-with? 41 | (:out (planck.shell/sh "pwd" :dir (planck.io/file "script"))) 42 | "script\n")) 43 | (let [rv (planck.shell/sh "pwd" :dir "bogus")] 44 | (is (not= 0 (:exit rv))))) 45 | 46 | (deftest inline-env-test 47 | (is (= "FOO=BAR\n" (:out (planck.shell/sh "env" :env {"FOO" "BAR"}))))) 48 | 49 | (deftest with-sh-env-test 50 | (is (= "FOO=BAR\n" (:out (planck.shell/with-sh-env {"FOO" "BAR"} 51 | (planck.shell/sh "env")))))) 52 | 53 | (deftest with-sh-env-throws-on-nil-env-vars-test 54 | (is (thrown-with-msg? js/Error 55 | #"planck.shell/string-string-map\?" 56 | (planck.shell/with-sh-env {nil "value-for-a-nil-key"} 57 | (planck.shell/sh "env")))) 58 | (is (thrown-with-msg? js/Error 59 | #"planck.shell/string-string-map\?" 60 | (planck.shell/with-sh-env {"key-with-a-nil-value" nil} 61 | (planck.shell/sh "env"))))) 62 | 63 | (deftest launch-fail-ex-info-test 64 | (try 65 | (planck.shell/sh "env" "abc") 66 | (catch :default e 67 | (let [expected-errors #{"env: abc: No such file or directory" 68 | "env: ‘abc’: No such file or directory" 69 | "env: 'abc': No such file or directory"}] 70 | (is (= 127 (:exit (ex-data e)))) 71 | (is (= "" (:out (ex-data e)))) 72 | (is (contains? expected-errors (string/trim (:err (ex-data e))))) 73 | (is (contains? expected-errors (ex-message e))))))) 74 | 75 | (deftest launch-fail-msg-test 76 | (is (= "Launch path \"ls -l\" not accessible. Did you perhaps mean to launch using \"ls\", with (\"-l\") as arguments?" 77 | (#'planck.shell/launch-fail-msg "ls -l")))) 78 | 79 | (deftest cat-in-test 80 | (let [test-str (pr-str (range 1e5)) 81 | test-file (io/file "/tmp/plnk-cat-in-test.txt")] 82 | (planck.core/spit test-file test-str) 83 | (let [result (planck.shell/sh "cat" :in test-file)] 84 | (is (= test-str (:out result)))))) 85 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/from/io/aviso/ansi.clj: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.from.io.aviso.ansi 2 | (:require 3 | [clojure.string :as str])) 4 | 5 | (defn ^:private def-sgr-const 6 | "Utility for defining a font-modifying constant." 7 | [symbol-name color-name & codes] 8 | `(def ^:const ~(symbol symbol-name) 9 | ~(str "Constant for ANSI code to enable " color-name " text.") 10 | (str csi ~(str/join ";" codes) sgr))) 11 | 12 | (defn ^:private def-sgr-fn 13 | "Utility for creating a function that enables some combination of SGR codes around some text, but resets 14 | the font after the text." 15 | [fn-name color-name & codes] 16 | `(defn ~(symbol fn-name) 17 | ~(str "Wraps the provided text with ANSI codes to render as " color-name " text.") 18 | [~'text] 19 | (str (str csi ~(str/join ";" codes) sgr) ~'text (str csi sgr)))) 20 | 21 | ;;; Define functions and constants for each color. The functions accept a string 22 | ;;; and wrap it with the ANSI codes to set up a rendition before the text, 23 | ;;; and reset the rendition fully back to normal after. 24 | ;;; The constants enable the rendition, and require the reset-font value to 25 | ;;; return to normal. 26 | ;;; For each color C: 27 | ;;; - functions: 28 | ;;; - C: change text to that color (e.g., "green") 29 | ;;; - C-bg: change background to that color (e.g., "green-bg") 30 | ;;; - bold-C: change text to bold variation of color (e.g., "bold-green") 31 | ;;; - bold-C-bg: change background to bold variation of color (e.g., "bold-green-bg") 32 | ;;; - constants 33 | ;;; - C-font: enable text in that color (e.g., "green-font") 34 | ;;; - C-bg-font: enable background in that color (e.g., "green-bg-font") 35 | ;;; - bold-C-font; enable bold text in that color (e.g., "bold-green-font") 36 | ;;; - bold-C-bg-font; enable background in that bold color (e.g., "bold-green-bg-font") 37 | 38 | (defmacro generate-color-functions [] 39 | (let [generate-functions-for-index-color (fn [index color-name] 40 | [(def-sgr-fn color-name color-name (+ 30 index)) 41 | (def-sgr-fn (str color-name "-bg") (str color-name " background") (+ 40 index)) 42 | (def-sgr-fn (str "bold-" color-name) (str "bold " color-name) 1 (+ 30 index)) 43 | (def-sgr-fn (str "bold-" color-name "-bg") (str "bold " color-name " background") 1 (+ 40 index)) 44 | (def-sgr-const (str color-name "-font") color-name (+ 30 index)) 45 | (def-sgr-const (str color-name "-bg-font") (str color-name " background") (+ 40 index)) 46 | (def-sgr-const (str "bold-" color-name "-font") (str "bold " color-name) 1 (+ 30 index)) 47 | (def-sgr-const (str "bold-" color-name "-bg-font") (str "bold " color-name " background") 1 (+ 40 index))])] 48 | `(do 49 | ~@(generate-functions-for-index-color 0 "black") 50 | ~@(generate-functions-for-index-color 1 "red") 51 | ~@(generate-functions-for-index-color 2 "green") 52 | ~@(generate-functions-for-index-color 3 "yellow") 53 | ~@(generate-functions-for-index-color 4 "blue") 54 | ~@(generate-functions-for-index-color 5 "magenta") 55 | ~@(generate-functions-for-index-color 6 "cyan") 56 | ~@(generate-functions-for-index-color 7 "white")))) 57 | 58 | ;; ANSI defines quite a few more, but we're limiting to those that display properly in the 59 | ;; Cursive REPL. 60 | -------------------------------------------------------------------------------- /planck-c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(planck C) 3 | 4 | set(CMAKE_BUILD_TYPE Release) 5 | 6 | # Uncomment to enable Clang Address Sanitizer 7 | #set (CMAKE_C_FLAGS "-fsanitize=address -O1 -fno-omit-frame-pointer") 8 | 9 | add_compile_options(-Wall) 10 | 11 | if(DEFINED ENV{WARN_ERROR_BUILD}) 12 | add_compile_options(-Werror) 13 | endif() 14 | 15 | set(SOURCE_FILES 16 | archive.c 17 | archive.h 18 | bundle.c 19 | bundle.h 20 | bundle_inflate.h 21 | clock.c 22 | clock.h 23 | edn.c 24 | edn.h 25 | engine.c 26 | engine.h 27 | file.c 28 | file.h 29 | functions.c 30 | functions.h 31 | globals.h 32 | http.c 33 | http.h 34 | io.c 35 | io.h 36 | jsc_utils.c 37 | jsc_utils.h 38 | keymap.c 39 | keymap.h 40 | legal.c 41 | legal.h 42 | linenoise.c 43 | linenoise.h 44 | main.c 45 | repl.c 46 | repl.h 47 | shell.c 48 | shell.h 49 | sockets.c 50 | sockets.h 51 | str.c 52 | str.h 53 | tasks.c 54 | tasks.h 55 | theme.c 56 | theme.h 57 | timers.c 58 | timers.h) 59 | 60 | add_executable(planck ${SOURCE_FILES}) 61 | 62 | find_package(PkgConfig REQUIRED) 63 | 64 | FIND_PACKAGE(CURL) 65 | if(CURL_FOUND) 66 | include_directories(${CURL_INCLUDE_DIR}) 67 | target_link_libraries(planck ${CURL_LIBRARIES}) 68 | else(CURL_FOUND) 69 | find_program(CURL_CONFIG curl-config) 70 | if(CURL_CONFIG) 71 | execute_process(COMMAND ${CURL_CONFIG} --cflags 72 | OUTPUT_VARIABLE CURL_CFLAGS 73 | OUTPUT_STRIP_TRAILING_WHITESPACE) 74 | execute_process(COMMAND ${CURL_CONFIG} --libs 75 | OUTPUT_VARIABLE CURL_LIBS 76 | OUTPUT_STRIP_TRAILING_WHITESPACE) 77 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${CURL_CFLAGS}") 78 | target_link_libraries(planck ${CURL_LIBS}) 79 | else(CURL_CONFIG) 80 | find_library(CURL curl) 81 | target_link_libraries(planck ${CURL}) 82 | endif(CURL_CONFIG) 83 | endif(CURL_FOUND) 84 | 85 | pkg_check_modules(ZLIB REQUIRED zlib) 86 | include_directories(${ZLIB_INCLUDE_DIRS}) 87 | target_link_libraries(planck ${ZLIB_LDFLAGS}) 88 | 89 | pkg_check_modules(LIBZIP REQUIRED libzip) 90 | include_directories(${LIBZIP_INCLUDE_DIRS}) 91 | target_link_libraries(planck ${LIBZIP_LDFLAGS}) 92 | 93 | if(APPLE) 94 | find_library(JAVASCRIPTCORE JavaScriptCore) 95 | mark_as_advanced(JAVASCRIPTCORE) 96 | target_link_libraries(planck ${JAVASCRIPTCORE}) 97 | elseif(UNIX) 98 | pkg_check_modules(JAVASCRIPTCORE javascriptcoregtk-4.0) 99 | if(NOT JAVASCRIPTCORE_FOUND) 100 | pkg_check_modules(JAVASCRIPTCORE REQUIRED javascriptcoregtk-3.0) 101 | add_definitions(-DJAVASCRIPT_CORE_3) 102 | endif(NOT JAVASCRIPTCORE_FOUND) 103 | include_directories(${JAVASCRIPTCORE_INCLUDE_DIRS}) 104 | target_link_libraries(planck ${JAVASCRIPTCORE_LDFLAGS}) 105 | endif(APPLE) 106 | 107 | if(APPLE) 108 | add_definitions(-DU_DISABLE_RENAMING) 109 | include_directories(/usr/local/opt/icu4c/include) 110 | find_library(ICU4C icucore) 111 | target_link_libraries(planck ${ICU4C}) 112 | elseif(UNIX) 113 | pkg_check_modules(ICU_UC REQUIRED icu-uc) 114 | pkg_check_modules(ICU_IO REQUIRED icu-io) 115 | include_directories(${ICU_UC_INCLUDE_DIRS} ${ICU_IO_INCLUDE_DIRS}) 116 | target_link_libraries(planck ${ICU_UC_LDFLAGS} ${ICU_IO_LDFLAGS}) 117 | endif(APPLE) 118 | 119 | if(APPLE) 120 | elseif(UNIX) 121 | find_package(Threads) 122 | target_link_libraries(planck ${CMAKE_THREAD_LIBS_INIT}) 123 | endif(APPLE) 124 | -------------------------------------------------------------------------------- /planck-c/linenoise.h: -------------------------------------------------------------------------------- 1 | /* linenoise.h -- VERSION 1.0 2 | * 3 | * Guerrilla line editing library against the idea that a line editing lib 4 | * needs to be 20,000 lines of C code. 5 | * 6 | * See linenoise.c for more information. 7 | * 8 | * ------------------------------------------------------------------------ 9 | * 10 | * Copyright (c) 2010-2014, Salvatore Sanfilippo 11 | * Copyright (c) 2010-2013, Pieter Noordhuis 12 | * 13 | * All rights reserved. 14 | * 15 | * Redistribution and use in source and binary forms, with or without 16 | * modification, are permitted provided that the following conditions are 17 | * met: 18 | * 19 | * * Redistributions of source code must retain the above copyright 20 | * notice, this list of conditions and the following disclaimer. 21 | * 22 | * * Redistributions in binary form must reproduce the above copyright 23 | * notice, this list of conditions and the following disclaimer in the 24 | * documentation and/or other materials provided with the distribution. 25 | * 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 27 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 28 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 29 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 30 | * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 31 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 32 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 33 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 34 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 35 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 36 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 37 | */ 38 | 39 | #ifndef __LINENOISE_H 40 | #define __LINENOISE_H 41 | 42 | #ifdef __cplusplus 43 | extern "C" { 44 | #endif 45 | 46 | void linenoiseSetupSigWinchHandler(); 47 | 48 | typedef struct linenoiseCompletions { 49 | size_t len; 50 | char **cvec; 51 | } linenoiseCompletions; 52 | 53 | typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); 54 | 55 | void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); 56 | 57 | void linenoiseAddCompletion(linenoiseCompletions *, const char *); 58 | 59 | typedef void(linenoiseHighlightCallback)(const char *, int pos); 60 | 61 | void linenoiseSetHighlightCallback(linenoiseHighlightCallback *); 62 | 63 | typedef void(linenoiseHighlightCancelCallback)(); 64 | 65 | void linenoiseSetHighlightCancelCallback(linenoiseHighlightCancelCallback *); 66 | 67 | void linenoisePrintNow(const char *text); 68 | 69 | char *linenoise(const char *prompt, const char *secondary_prompt, const char *promptAnsiCode, int spaces); 70 | 71 | int linenoiseHistoryAdd(const char *line); 72 | 73 | int linenoiseHistorySetMaxLen(int len); 74 | 75 | int linenoiseHistorySave(const char *filename); 76 | 77 | int linenoiseHistoryLoad(const char *filename); 78 | 79 | void linenoiseClearScreen(void); 80 | 81 | void linenoiseSetMultiLine(int ml); 82 | 83 | void linenoisePrintKeyCodes(void); 84 | 85 | void linenoiseSetKeymapEntry(int action, char key); 86 | 87 | int is_pasting(); 88 | 89 | #define KM_GO_TO_START_OF_LINE 0 90 | #define KM_MOVE_LEFT 1 91 | #define KM_CANCEL 2 92 | #define KM_DELETE_RIGHT 3 93 | #define KM_GO_TO_END_OF_LINE 4 94 | #define KM_MOVE_RIGHT 5 95 | #define KM_DELETE 6 96 | #define KM_TAB 7 97 | #define KM_DELETE_TO_END_OF_LINE 8 98 | #define KM_CLEAR_SCREEN 9 99 | #define KM_ENTER 10 100 | #define KM_HISTORY_NEXT 11 101 | #define KM_HISTORY_PREVIOUS 12 102 | #define KM_SWAP_CHARS 13 103 | #define KM_CLEAR_LINE 14 104 | #define KM_DELETE_PREVIOUS_WORD 15 105 | #define KM_ESC 16 106 | #define KM_BACKSPACE 17 107 | #define KM_REVERSE_I_SEARCH 18 108 | #define KM_CANCEL_SEARCH 19 109 | #define KM_FINISH_SEARCH 20 110 | 111 | #ifdef __cplusplus 112 | } 113 | #endif 114 | 115 | #endif /* __LINENOISE_H */ 116 | -------------------------------------------------------------------------------- /doc/source-dev.md: -------------------------------------------------------------------------------- 1 | ## Source Dev 2 | 3 | 4 | 5 | When launching Planck using `plk`, you can specify a vector of source directories in `deps.edn`: 6 | 7 | ``` 8 | {:paths ["src" "test"]} 9 | ``` 10 | 11 | With this, you can put this code in `src/foo/core.cljs`: 12 | 13 | ```clojure 14 | (ns foo.core) 15 | 16 | (defn square 17 | [x] 18 | (* x x)) 19 | ``` 20 | 21 | Then, if you launch Planck via `plk` 22 | 23 | ``` 24 | $ plk 25 | ``` 26 | 27 | you can then load the code in `foo.core` by doing 28 | 29 | ``` 30 | cljs.user=> (require 'foo.core) 31 | ``` 32 | 33 | If you subsequently edit `src/foo/core.cljs`, say, to define a new function, or to change existing functions, you can reload that code by adding the `:reload` flag: 34 | 35 | ``` 36 | cljs.user=> (require 'foo.core :reload) 37 | ``` 38 | 39 | Alternatively, when launching Planck you can use the `-c` or `-​-​classpath` option, or the `PLANCK_CLASSPATH` environment variable, to specify a colon-delimited list of source directories and JARs to search in when loading code using `require` and `require-macros`. You can also use `-D` or `-​-​dependencies` provide a comma separated list of `SYM:VERSION`, indicating libraries to be loaded from the local Maven repository. (See the Dependencies section of this guide.) 40 | 41 | Using `-c`, you can specify `"src"` and `"test"` as source directories via 42 | 43 | ``` 44 | planck -c src:test 45 | ``` 46 | 47 | ### Macros 48 | 49 | If you define macros in bootstrap ClojureScript (which is the mode that Planck runs in), the macros must be written in ClojureScript (as opposed to Clojure, as is done with regular ClojureScript). 50 | 51 | Even though the macros are defined in ClojureScript, they are defined in `*.clj` files. You can, if you wish, also define macros in `*.cljc` files, but when they are processed, the `:cljs` branch of reader conditionals will be used. 52 | 53 | When writing macros for self-hosted ClojureScript, they must abide the same rules that apply to all ClojureScript code. In particular, this means a macro cannot call another macro defined in the same _compilation stage_: If a macro calls another macro _during_ expansion, then one approach is to define the called macro in a “higher” namespace (possibly arranged in a tower). On the other hand, if a macro simply _expands_ to a call to another macro defined in the same namespace, then the compilation staging rules are satisfied. 54 | 55 | ### Source Mapping 56 | 57 | If an exception is thrown, you may see a stack trace. (If not, you can use `pst` to print the stack trace for an exception.) When trace lines correspond to code that originated from files, the line numbers are mapped from the executed JavaScript back to the original ClojureScript. 58 | 59 | ### Tagged Literals 60 | 61 | Planck supports tagged literals. For an overview of this feature see the [Tagged Literals](https://clojure.org/reference/reader#tagged_literals) documentation. 62 | 63 | Planck searches for `data_readers.cljc` files at the root of the classpath, and the values of the data reader maps are associated with vars that must defined in ClojureScript. 64 | 65 | For example, lets say a `data_readers.cljc` file contains: 66 | 67 | ``` 68 | {foo/bar my.project.foo/bar} 69 | ``` 70 | 71 | Then, in order to parse `#foo/bar [1 2 3]`, `#'my.project.foo/bar` must be defined in ClojureScript. (This differs from JVM ClojureScript, where this must be defined in Clojure.) This could be accomplished by defining a namespace like the following that is loaded into Planck before expressions involving `#foo/bar` are read. 72 | 73 | ``` 74 | (ns my.project.foo) 75 | 76 | (defn bar [x] ,,,) 77 | ``` 78 | 79 | Note that, in either case (JVM ClojureScript, or self-hosted ClojureScript), the reader function `bar` above must return code that is compilable in ClojureScript. 80 | 81 | ### Bootstrap ClojureScript 82 | 83 | It is possible to make use of the `cljs.js` namespace within Planck. But, since Planck is built with the `:dump-core` ClojureScript compiler option set to `false`, calls to the 0-arity version of `cljs.js/empty-state` will produce a state atom which lacks `cljs.core` analysis metadata. To produce a populated compiler state atom, you can make use of `planck.core/init-empty-state`: 84 | 85 | ``` 86 | (require 'cljs.js 'planck.core) 87 | 88 | (def st (cljs.js/empty-state planck.core/init-empty-state)) 89 | 90 | (cljs.js/eval-str st "(map inc [1 2 3])" nil 91 | {:eval cljs.js/js-eval :context :expr} identity) 92 | ``` 93 | -------------------------------------------------------------------------------- /planck-cljs/script/build.clj: -------------------------------------------------------------------------------- 1 | (ns script.bootstrap.build 2 | (:require [clojure.java.io :as io] 3 | [cljs.build.api :as api] 4 | [cljs.analyzer :as ana] 5 | [cljs.env :as env] 6 | [cljs.compiler :as comp] 7 | [cognitect.transit :as transit]) 8 | (:import [java.io ByteArrayOutputStream File FileInputStream])) 9 | 10 | (def sandbox-build? (boolean (System/getenv "SANDBOX_BUILD"))) 11 | 12 | (def experimental-warnings #{:recur-type-mismatch :private-var-access}) 13 | 14 | (def non-fatal-warnings (into #{:redef} experimental-warnings)) 15 | 16 | (defn delete-recursively [fname] 17 | (doseq [f (reverse (file-seq (io/file fname)))] 18 | (io/delete-file f true))) 19 | 20 | (cljs.analyzer/with-warning-handlers 21 | [(fn [warning-type env extra] 22 | (when (warning-type cljs.analyzer/*cljs-warnings*) 23 | (when-let [s (cljs.analyzer/error-message warning-type extra)] 24 | (binding [*out* *err*] 25 | (println "WARNING:" (cljs.analyzer/message env s))) 26 | (when-not (warning-type non-fatal-warnings) 27 | (System/exit 1)))))] 28 | (api/build (api/inputs "src") 29 | {:output-dir "out" 30 | :output-to "out/main.js" 31 | :optimizations :none 32 | :static-fns true 33 | :optimize-constants false 34 | :dump-core false 35 | :parallel-build true 36 | :libs ["lib/closure" 37 | "lib/third_party/closure"] 38 | :foreign-libs [{:file "jscomp.js" 39 | :provides ["google-closure-compiler-js"]} 40 | {:file "paredit.js" 41 | :provides ["paredit"] 42 | :global-exports '{paredit paredit}}] 43 | :compiler-stats false 44 | :aot-cache (not sandbox-build?)})) 45 | 46 | (defn copy-source 47 | [filename] 48 | (spit (str "out/" filename) 49 | (slurp (io/resource filename)))) 50 | 51 | (copy-source "cljs/test.cljc") 52 | (copy-source "cljs/pprint.cljc") 53 | (copy-source "cljs/spec/alpha.cljc") 54 | (copy-source "cljs/spec/test/alpha.cljc") 55 | (copy-source "cljs/spec/test/alpha.cljs") 56 | (copy-source "cljs/spec/gen/alpha.cljc") 57 | (copy-source "cljs/analyzer/api.cljc") 58 | (copy-source "cljs/analyzer/macros.clj") 59 | (copy-source "cljs/compiler/macros.clj") 60 | (copy-source "cljs/env/macros.clj") 61 | (copy-source "clojure/template.clj") 62 | 63 | (try 64 | (copy-source "cljs/core/specs/alpha.cljc") 65 | (copy-source "cljs/core/specs/alpha.cljs") 66 | (catch Throwable _)) 67 | 68 | (defn write-cache [cache out-path] 69 | (let [out (ByteArrayOutputStream. 1000000) 70 | writer (transit/writer out :json)] 71 | (transit/write writer cache) 72 | (spit (io/file out-path) (.toString out)))) 73 | 74 | ;; Needed in the case that we are depending on a 75 | ;; ClojureScript source tree via deps.edn (instead of 76 | ;; on a built ClojureScript JAR). This is a rough copy 77 | ;; of the functionality in cljs.closure/aot-cache-core 78 | (defn aot-cache-core [] 79 | (let [src (io/file (.getFile (io/resource "cljs/core.cljs"))) 80 | base (io/file "/tmp/cljs-aot-cache") 81 | dest (io/file base "core.aot.js") 82 | cache (io/file base "core.cljs.cache.aot.edn")] 83 | (io/make-parents dest) 84 | (env/with-compiler-env (env/default-compiler-env {:infer-externs true}) 85 | (comp/compile-file src dest 86 | {:source-map true 87 | :source-map-url "core.js.map" 88 | :output-dir (str "src" File/separator "main" File/separator "cljs")}) 89 | (ana/write-analysis-cache 'cljs.core cache src)))) 90 | 91 | (try 92 | (let [res (or (io/resource "cljs/core.cljs.cache.aot.edn") 93 | (do (aot-cache-core) 94 | "/tmp/cljs-aot-cache/core.cljs.cache.aot.edn")) 95 | cache (read-string (slurp res))] 96 | (doseq [key (keys cache)] 97 | (write-cache (key cache) (str "out/cljs/core.cljs.cache.aot." (munge key) ".json")))) 98 | (finally 99 | (when (.exists (io/file "/tmp/cljs-aot-cache")) 100 | (delete-recursively (io/file "/tmp/cljs-aot-cache"))))) 101 | 102 | (let [res "out/cljs/core$macros.cljc.cache.json" 103 | cache (transit/read (transit/reader (FileInputStream. res) :json))] 104 | (doseq [key (keys cache)] 105 | (write-cache (key cache) (str "out/cljs/core$macros.cljc.cache." (munge key) ".json")))) 106 | 107 | (System/exit 0) 108 | -------------------------------------------------------------------------------- /planck-c/keymap.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "edn.h" 7 | #include "linenoise.h" 8 | #include "str.h" 9 | 10 | int id_for_key_map_action(char *action) { 11 | 12 | if (strcmp(action, ":go-to-beginning-of-line") == 0) { 13 | return KM_GO_TO_START_OF_LINE; 14 | } else if (strcmp(action, ":go-back-one-space") == 0) { 15 | return KM_MOVE_LEFT; 16 | } else if (strcmp(action, ":go-forward-one-space") == 0) { 17 | return KM_MOVE_RIGHT; 18 | } else if (strcmp(action, ":delete-right") == 0) { 19 | return KM_DELETE_RIGHT; 20 | } else if (strcmp(action, ":delete-backwards") == 0) { 21 | return KM_DELETE; 22 | } else if (strcmp(action, ":delete-to-end-of-line") == 0) { 23 | return KM_DELETE_TO_END_OF_LINE; 24 | } else if (strcmp(action, ":go-to-end-of-line") == 0) { 25 | return KM_GO_TO_END_OF_LINE; 26 | } else if (strcmp(action, ":clear-screen") == 0) { 27 | return KM_CLEAR_SCREEN; 28 | } else if (strcmp(action, ":next-line") == 0) { 29 | return KM_HISTORY_NEXT; 30 | } else if (strcmp(action, ":previous-line") == 0) { 31 | return KM_HISTORY_PREVIOUS; 32 | } else if (strcmp(action, ":transpose-characters") == 0) { 33 | return KM_SWAP_CHARS; 34 | } else if (strcmp(action, ":undo-typing-on-line") == 0) { 35 | return KM_CLEAR_LINE; 36 | } else if (strcmp(action, ":delete-previous-word") == 0) { 37 | return KM_DELETE_PREVIOUS_WORD; 38 | } else if (strcmp(action, ":reverse-i-search") == 0) { 39 | return KM_REVERSE_I_SEARCH; 40 | } else if (strcmp(action, ":cancel-search") == 0) { 41 | return KM_CANCEL_SEARCH; 42 | } else if (strcmp(action, ":finish-search") == 0) { 43 | return KM_FINISH_SEARCH; 44 | } else { 45 | return -1; 46 | } 47 | } 48 | 49 | char key_code_for(char *key_name) { 50 | if (str_has_prefix(key_name, ":ctrl-") == 0 && strlen(key_name) == 7) { 51 | char c = key_name[6]; 52 | if ('a' <= c && c <= 'z') { 53 | return (char) (c - 'a' + 1); 54 | } else { 55 | return -1; 56 | } 57 | } 58 | return -1; 59 | } 60 | 61 | static int last_id = -2; 62 | static char buffer[1024]; 63 | 64 | static FILE *reader_file = NULL; 65 | 66 | static wint_t reader_getwchar(const clj_Reader *r) { 67 | return fgetwc(reader_file); 68 | } 69 | 70 | static bool in_map = false; 71 | static char *keymap_path = NULL; 72 | 73 | extern void emit(const clj_Reader *r, const clj_Node *n) { 74 | if (n->type == CLJ_MAP) { 75 | in_map = true; 76 | } else if (n->type == CLJ_END) { 77 | in_map = false; 78 | } else if (n->type == CLJ_KEYWORD) { 79 | wcstombs(buffer, n->value, 1024); 80 | if (last_id == -2) { 81 | last_id = id_for_key_map_action(buffer); 82 | if (last_id == -1) { 83 | fprintf(stderr, "%s: Unrecognized keymap key: %s\n", keymap_path, buffer); 84 | } 85 | } else { 86 | char key_code = key_code_for(buffer); 87 | if (key_code == -1) { 88 | fprintf(stderr, "%s: Unrecognized keymap value: %s\n", keymap_path, buffer); 89 | } 90 | if (last_id != -1 && key_code != -1) { 91 | linenoiseSetKeymapEntry(last_id, key_code); 92 | } 93 | last_id = -2; 94 | } 95 | } 96 | } 97 | 98 | int load_keymap(char *home) { 99 | 100 | char keymap_name[] = ".planck_keymap"; 101 | size_t len = strlen(home) + strlen(keymap_name) + 2; 102 | keymap_path = malloc(len * sizeof(char)); 103 | snprintf(keymap_path, len, "%s/%s", home, keymap_name); 104 | 105 | reader_file = fopen(keymap_path, "r"); 106 | if (reader_file) { 107 | clj_Result result; 108 | char message[200]; 109 | 110 | clj_Reader reader; 111 | reader.emit = emit; 112 | reader.getwchar = reader_getwchar; 113 | 114 | while (1) { 115 | result = clj_read(&reader); 116 | switch (result) { 117 | case CLJ_MORE: 118 | break; 119 | case CLJ_EOF: 120 | free(keymap_path); 121 | fclose(reader_file); 122 | return EXIT_SUCCESS; 123 | default: 124 | free(keymap_path); 125 | fclose(reader_file); 126 | clj_read_error(message, &reader, result); 127 | fprintf(stderr, "%s\n", message); 128 | return EXIT_FAILURE; 129 | } 130 | } 131 | 132 | } 133 | 134 | return EXIT_SUCCESS; 135 | } -------------------------------------------------------------------------------- /doc/internals.md: -------------------------------------------------------------------------------- 1 | ## Internals 2 | 3 | 4 | 5 | How does Planck work? 6 | 7 | ### Fundamentals 8 | 9 | At a high level, there is no JVM involved. Planck makes use of ClojureScript's [self-hosting](http://swannodette.github.io/2015/07/29/clojurescript-17) capability and employs JavaScriptCore as its execution environment. JavaScriptCore is the JavaScript engine used by Safari and is already installed on all modern Macs. 10 | 11 | When you launch Planck, it internally starts a JavaScriptCore instance and then loads JavaScript implementing the ClojureScript runtime. This JavaScript is [baked into](http://blog.fikesfarm.com/posts/2015-07-27-island-repl.html) the Planck binary. 12 | 13 | By default, Planck then starts a REPL. Planck makes entering expressions a little easier by employing [a library](https://github.com/antirez/linenoise), making it possible to edit the line as well as access previously entered lines by using the up arrow. 14 | 15 | Planck enhances this experience by providing tab completion and brace highlighting: 16 | 17 | * When you press the tab key, Planck executes some JavaScript that finds candidate ClojureScript symbols for completions, given what you've currently typed. 18 | * When you type a closing `)`, `]`, or `}` character, Planck executes some JavaScript to find the matching counterpart. If it exists, Planck temporarily moves the cursor over that character. 19 | 20 | (The JavaScript for both of these actions is baked into the binary as well, and is originally sourced from ClojureScript.) 21 | 22 | When you enter a complete form, ClojureScript's self-hosting kicks in: The text of the form is passed to the ClojureScript compiler (which is already loaded into JavaScriptCore, pre-compiled as JavaScript). This results in JavaScript that _evaluates_ the form. 23 | 24 | The form’s JavaScript is then executed. You can actually see the JavaScript if you start Planck in verbose mode by passing `-v`: 25 | 26 | ``` 27 | $ planck -v 28 | cljs.user=> (+ 2 3) 29 | Evaluating (+ 2 3) 30 | (2 + 3) 31 | 5 32 | ``` 33 | 34 | Entering a slightly more complicated expression, you can see that the emitted JavaScript makes use of the ClojureScript runtime: 35 | 36 | ``` 37 | cljs.user=> (first [4 7]) 38 | Evaluating (first [4 7]) 39 | cljs.core.first.call(null,new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [4,7], null)) 40 | 4 41 | ``` 42 | 43 | ### Side Effects 44 | 45 | That's cool when evaluating pure expressions. What about interacting with the outside environment? 46 | 47 | Let's say you want to read the content of a file you have on disk, and you enter these forms: 48 | 49 | ```clojure 50 | (require '[planck.core :refer [slurp]]) 51 | (slurp "foo.txt") 52 | ``` 53 | 54 | At the bottom, Planck has implemented certain I/O primitives and has exposed them as JavaScript functions in JavaScriptCore. One such primitive opens a file for reading. Planck has some code like this 55 | 56 | ```objectivec 57 | self.inputStream = [NSInputStream 58 | inputStreamWithFileAtPath:path] 59 | ``` 60 | 61 | in a "file reader" Objective-C class. The constructor for this class is exposed in JavaScript as a function with the name `PLANCK_FILE_READER_OPEN`. This capability is made available to you in ClojureScript by having functions like `slurp` employ ClojureScript code like 62 | 63 | ```clojure 64 | (js/PLANCK_FILE_READER_OPEN "foo.txt") 65 | ``` 66 | 67 | To actually read from the file, `slurp` calls another `js/PLANCK_FILE_READER_READ` primitive, which invokes code like 68 | 69 | ```objectivec 70 | [self.inputStream read:buf 71 | maxLength:1024] 72 | ``` 73 | 74 | A few Planck ClojureScript namespaces are bundled with Planck in order to provide mappings onto these I/O primitives, exposing the simple APIs—like `slurp`—that you are familiar with: `planck.core`, `planck.io`, and `planck.shell`. 75 | 76 | In a nutshell, that’s really a big part of what Planck _is_: Some glue between ClojureScript and the native host environment. 77 | 78 | ### Affordances 79 | 80 | Planck wraps all this with some niceties making it suitable as a scripting environment. 81 | 82 | One aspect is the loading of custom ClojureScript source files. Let's say you have `src/my_cool_code/core.cljs`, and at the REPL you invoke 83 | 84 | ```clojure 85 | (require 'my-cool-code.core) 86 | ``` 87 | 88 | Planck implements the `require` “REPL special form,” which causes bootstrapped ClojureScript—specifically `cljs.js`, via its `*load-fn*`—to load your source from disk (by using Objective-C I/O primitives exposed as JavaScript). 89 | 90 | The nice thing is that `*load-fn*` is also used for `:require` specs that may appear in namespace declarations in your code, as well as `require-macros` and `import`. 91 | 92 | To top it off, Planck is free to implement `*load-fn*` in convenient ways: 93 | * It loads its own namespaces (like `planck.core`) directly from gzipped pre-compiled JavaScript baked in the binary. 94 | * It also loads code from JAR files: Planck can be provided a classpath, specifying directories and JAR files to be searched when satisfying a load request. 95 | 96 | With the ability to dynamically load custom ClojureScript code, executing it by mapping it onto native I/O facilities, can ClojureScript can be used as a compelling alternative for your Bash shell scripting needs. 97 | -------------------------------------------------------------------------------- /int-test/script/int-tests: -------------------------------------------------------------------------------- 1 | #!build/Release/planck -k int-test/cache 2 | (ns planck.int-tests 3 | (:require [planck.core :refer [exit]] 4 | [planck.shell :refer [sh *sh-dir*]])) 5 | 6 | ;; Perhaps this file can evolve to be more like 7 | ;; cljs.test, and perhaps it can involve multiple 8 | ;; namespaces, etc. 9 | ;; 10 | ;; For now, all you ned to do to cause the tests 11 | ;; to fail is call `(exit 1)`. 12 | 13 | (def planck-exe "planck-c/build/planck") 14 | 15 | (defn check-output 16 | [expected shell-output] 17 | (when-not (= expected (:out shell-output)) 18 | (println "Expected:") 19 | (prn expected) 20 | (println "Actual:") 21 | (prn (:out shell-output)) 22 | (exit 1))) 23 | 24 | (check-output 25 | (str 26 | "Planck " planck.core/*planck-version* " 27 | 28 | Usage: planck [init-opt*] [main-opt] [arg*] 29 | 30 | With no options or args, runs an interactive Read-Eval-Print Loop 31 | 32 | init options: 33 | -i path, --init=path Load a file or resource 34 | -e string, --eval=string Evaluate expressions in string; print non-nil 35 | values 36 | -c cp, --classpath=cp Use colon-delimited cp for source directories and 37 | JARs. PLANCK_CLASSPATH env var may be used instead. 38 | -K, --auto-cache Create and use .planck_cache dir for cache 39 | -k path, --cache=path If dir exists at path, use it for cache 40 | -q, --quiet Quiet mode 41 | -v, --verbose Emit verbose diagnostic output 42 | -d, --dumb-terminal Disable line editing / VT100 terminal control 43 | -t theme, --theme=theme Set the color theme 44 | -n x, --socket-repl=x Enable socket REPL where x is port or IP:port 45 | -s, --static-fns Generate static dispatch function calls 46 | -a, --elide-asserts Set *assert* to false to remove asserts 47 | 48 | main options: 49 | -m ns-name, --main=ns-name Call the -main function from a namespace with 50 | args 51 | -r, --repl Run a repl 52 | path Run a script from a file or resource 53 | - Run a script from standard input 54 | -h, -?, --help Print this help message and exit 55 | -l, --legal Show legal info (licenses and copyrights) 56 | 57 | operation: 58 | 59 | - Enters the cljs.user namespace 60 | - Binds planck.core/*command-line-args* to a seq of strings containing 61 | command line args that appear after any main option 62 | - Runs all init options in order 63 | - Calls a -main function or runs a repl or script if requested 64 | 65 | The init options may be repeated and mixed freely, but must appear before 66 | any main option. 67 | 68 | Paths may be absolute or relative in the filesystem. 69 | 70 | A comprehensive User Guide for Planck can be found at https://planck-repl.org\n\n") 71 | (sh planck-exe "-h")) 72 | 73 | (defn ensure-output [expected quoted-commands] 74 | (check-output 75 | (prn-str expected) 76 | (apply sh planck-exe (cons "-d" (interleave 77 | (repeat "-e") 78 | (map pr-str quoted-commands)))))) 79 | 80 | ;; Ensure clojure.string loads 81 | (ensure-output "foo quux baz" 82 | ['(require '[clojure.string :as s]) 83 | '(s/replace "foo bar baz" #"bar" "quux")]) 84 | 85 | ;; Ensure clojure.set loads 86 | (ensure-output #{:b :d} 87 | ['(require '[clojure.set :as set]) 88 | '(set/intersection #{:a :b :c :d} #{:b :d :e})]) 89 | 90 | ;; Ensure clojure.walk loads 91 | (ensure-output '(5 3 1) 92 | ['(require '[clojure.walk :refer [walk]]) 93 | '(walk first reverse [ [1 2] [3 4] [5 6] ])]) 94 | 95 | ;; Ensure cljs.pprint loads 96 | (ensure-output "123,456,789" 97 | ['(require '[cljs.pprint :refer [cl-format]]) 98 | '(cl-format nil "~:d" 123456789)]) 99 | 100 | ;; Ensure we don't get a degenerate env 101 | (when (= "true\n" (:out (sh planck-exe "-e" "(require '[planck.shell :refer [sh]])" "-e" "(empty? (:out (sh \"env\")))"))) 102 | (println "empty env") 103 | (exit 1)) 104 | 105 | ;; Ensure env workd 106 | (ensure-output (into (sorted-map) {:err "", :exit 0, :out "ABC=123\n"}) 107 | ['(require '[planck.shell :refer [sh]]) 108 | '(into (sorted-map) (sh "env" :env {"ABC" "123"}))]) 109 | 110 | ;; Test import REPL special 111 | (ensure-output "http" 112 | ['(import '[goog Uri]) 113 | '(.-scheme_ (Uri. "http://foo.bar"))]) 114 | 115 | ;; Ensure require merges using alias 116 | (ensure-output "0123456789" 117 | ['(require '[clojure.string :as str :refer [join]]) 118 | '(require 'clojure.set) 119 | '(str/join (range 10))]) 120 | 121 | ;; Ensure require merges using refer 122 | (ensure-output "0123456789" 123 | ['(require '[clojure.string :as str :refer [join]]) 124 | '(require 'clojure.set) 125 | '(join (range 10))]) 126 | 127 | ;; Ensure slurp throws an error for failed read 128 | (sh "rm" "-f" "/tmp/no-such-animal") 129 | (ensure-output "SONIC BOOOOOOOOM!" 130 | ['(require '[planck.core :refer [slurp]]) 131 | '(try (slurp "/tmp/no-such-animal") (catch js/Error e "SONIC BOOOOOOOOM!"))]) 132 | 133 | ;; Ensure spit throws an error for failed write 134 | (sh "touch" "/tmp/readonly") 135 | (sh "chmod" "444" "/tmp/readonly") 136 | (ensure-output "SONIC BOOOOOOOOM!" 137 | ['(require '[planck.core :refer [spit]]) 138 | '(try (spit "/tmp/readonly" "foo") (catch js/Error e "SONIC BOOOOOOOOM!"))]) 139 | (sh "rm" "-f" "/tmp/readonly") 140 | 141 | ;; Check that 0 evaluates to true 142 | (ensure-output :truthy 143 | ['(if 0 :truthy :falsey)]) 144 | -------------------------------------------------------------------------------- /planck-cljs/test/planck/repl_test.cljs: -------------------------------------------------------------------------------- 1 | (ns planck.repl-test 2 | (:require-macros 3 | [planck.repl]) 4 | (:require 5 | [clojure.spec.alpha :as s] 6 | [clojure.test :refer [deftest is testing]] 7 | [clojure.test.check.clojure-test :refer-macros [defspec]] 8 | [clojure.test.check.generators :as gen] 9 | [clojure.test.check.properties :as prop :include-macros true] 10 | [goog :as g] 11 | [planck.repl :as repl])) 12 | 13 | (deftest get-highlight-coords 14 | (testing "Highlight coordinates" 15 | (is (= [0 0] (js->clj (#'planck.repl/get-highlight-coords 1 "[]" [])))) 16 | (is (= [0 1] (js->clj (#'planck.repl/get-highlight-coords 2 "[[]]" [])))) 17 | (is (= [0 0] (js->clj (#'planck.repl/get-highlight-coords 3 "[()]" [])))) 18 | (is (= [0 1] (js->clj (#'planck.repl/get-highlight-coords 2 " []" [])))) 19 | (is (= [1 0] (js->clj (#'planck.repl/get-highlight-coords 1 "]" ["["])))) 20 | (is (= [2 0] (js->clj (#'planck.repl/get-highlight-coords 1 "]" ["[" ""])))) 21 | (is (= [1 0] (js->clj (#'planck.repl/get-highlight-coords 1 "]" ["[" "["])))) 22 | (is (= [2 0] (js->clj (#'planck.repl/get-highlight-coords 1 "]" ["[" "[]"])))) 23 | (is (= [0 1] (js->clj (#'planck.repl/get-highlight-coords 2 "#{}" [])))) 24 | (is (= [0 0] (js->clj (#'planck.repl/get-highlight-coords 4 "[\"[\"]" [])))))) 25 | 26 | (deftest test-apropos 27 | (is (= '(cljs.core/ffirst) (planck.repl/apropos "ffirst"))) 28 | (is (= '(cljs.core/ffirst) (planck.repl/apropos ffirst))) 29 | (is (= '(cljs.core/ffirst cljs.core/nfirst) (planck.repl/apropos #"[a-z]+first"))) 30 | (is (= '(cljs.core/aget) (planck.repl/apropos "aget")))) 31 | 32 | (deftest test-dir-planck-repl 33 | (is (= "*pprint-results*\napropos\napropos*\ndir\ndir*\ndoc\ndoc*\nfind-doc\nfind-doc*\nget-arglists\npst\npst*\nsource\nsource*\n" 34 | (with-out-str (planck.repl/dir planck.repl))))) 35 | 36 | (deftest get-error-indicator-test 37 | (is (= " ^" 38 | (#'repl/get-error-column-indicator 39 | (ex-info @#'repl/could-not-eval-expr {} 40 | (ex-info "" {:tag :cljs/analysis-error 41 | :column 3})) 42 | "foo.core")))) 43 | 44 | (deftest undo-reader-conditional-whitespace-docstring-test 45 | (is (= "a\n b" (#'planck.repl/undo-reader-conditional-whitespace-docstring "a\n b")))) 46 | 47 | (deftest root-resource-test 48 | (is (= "/foo_bar_baz/boo/core" (#'planck.repl/root-resource 'foo-bar-baz.boo.core)))) 49 | 50 | (defspec add-drop-macros-suffix-test 51 | (prop/for-all [ns-sym gen/symbol] 52 | (= ns-sym (-> ns-sym 53 | (#'repl/add-macros-suffix) 54 | str 55 | (#'repl/drop-macros-suffix) 56 | symbol)))) 57 | 58 | (deftest get-arglists-test 59 | (is (= '([x] [x y] [x y & more]) (#'planck.repl/get-arglists "max"))) 60 | (is (nil? (#'planck.repl/get-arglists "bogus-undefined")))) 61 | 62 | (deftest completions-for-goog-ns 63 | (is (some #{"isArrayLike"} (#'planck.repl/completion-candidates-for-ns 'goog false))) 64 | (is (some #{"trimLeft"} (#'planck.repl/completion-candidates-for-ns 'goog.string false)))) 65 | 66 | (deftest doc-test 67 | (is (empty? (with-out-str (planck.repl/doc every))))) 68 | 69 | (deftest planck-native?-test 70 | (is (#'planck.repl/planck-native? 'PLANCK_SOCKET_CLOSE)) 71 | (is (not (#'planck.repl/planck-native? 'foobar)))) 72 | 73 | (deftest gensym?-test 74 | (is (#'planck.repl/gensym? (gensym))) 75 | (is (not (#'planck.repl/gensym? 'foobar)))) 76 | 77 | (deftest test-is-readable? 78 | (is (= (#'planck.repl/is-readable? "(") nil)) 79 | (is (= (#'planck.repl/is-readable? "(+ 1 2)") "")) 80 | (is (= (#'planck.repl/is-readable? "(+ 1 2) :foo") " :foo")) 81 | (is (= (#'planck.repl/is-readable? "") nil)) 82 | (is (= (#'planck.repl/is-readable? ")") ""))) 83 | 84 | (deftest repl-read-string-test 85 | (is (thrown? js/Error (#'planck.repl/repl-read-string "34f"))) 86 | (is (thrown? js/Error (#'planck.repl/repl-read-string "a:"))) 87 | (is (thrown? js/Error (#'planck.repl/repl-read-string "]"))) 88 | (try 89 | (#'planck.repl/repl-read-string "34f") 90 | (catch :default e 91 | (is (= :read-source (:clojure.error/phase (ex-data e))))))) 92 | 93 | (deftest strip-source-map-test 94 | (let [input-sm {0 {2 [{:line 1 :col 1} {:line 2 :col 2 :name "a"}] 95 | 3 [{:line 2 :col 2 :name "b"}]}} 96 | stripped {0 {2 [{:line 2 :col 2}]}}] 97 | (is (= stripped (#'planck.repl/strip-source-map input-sm))))) 98 | 99 | (deftest require-goog-test 100 | (is (false? (g/isArrayLike nil)))) 101 | 102 | (deftest issue-749-test 103 | (let [source "#!/usr/bin/env bash\n\"exec\" \"plk\" \"-Sdeps\" \"{:deps {org.clojure/tools.cli {:mvn/version \\\"0.3.7\\\"}}}\" \"-Ksf\" \"$0\" \"$@\"\n\n(ns repro.core\n (:require [clojure.tools.cli :refer [parse-opts]]))"] 104 | (is (= 'repro.core (#'planck.repl/extract-namespace source)))) 105 | (let [source "#!/usr/bin/env bash\n\"exec\" \"plk\" \"-Sdeps\" \"{:deps {org.clojure/tools.cli {:mvn/version \\\"0.3.7\\\"}}}\" \"-Ksf\" \"$0\" \"$@\"\n\n(require '[clojure.tools.cli :refer [parse-opts]])"] 106 | (is (= 'cljs.user (#'planck.repl/extract-namespace source)))) 107 | (let [source ":hello"] 108 | (is (= 'cljs.user (#'planck.repl/extract-namespace source))))) 109 | 110 | (deftest read-compile-optss-test 111 | (let [compile-optss #js ["{:a 1}" "@co0.edn" "{:b 2}" "@/co1.edn:@co2.edn" "{:a 3}"]] 112 | (is (= {:a 3, :b 17, :test1 :t, :test2 :z, :test3 :y, :test4 :j, :test5 :h} 113 | (#'planck.repl/read-compile-optss compile-optss))))) 114 | 115 | (defn my-int? [x] (int? x)) 116 | 117 | (deftest spec-describe-core-fns 118 | (is (= 'my-int? (s/describe my-int?))) 119 | (is (= 'int? (s/describe int?)))) 120 | -------------------------------------------------------------------------------- /planck-c/jsc_utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | #include "jsc_utils.h" 8 | 9 | JSStringRef to_string(JSContextRef ctx, JSValueRef val) { 10 | if (JSValueIsUndefined(ctx, val)) { 11 | return JSStringCreateWithUTF8CString("undefined"); 12 | } else if (JSValueIsNull(ctx, val)) { 13 | return JSStringCreateWithUTF8CString("null"); 14 | } else { 15 | JSStringRef to_string_name = JSStringCreateWithUTF8CString("toString"); 16 | JSObjectRef obj = JSValueToObject(ctx, val, NULL); 17 | JSValueRef to_string = JSObjectGetProperty(ctx, obj, to_string_name, NULL); 18 | JSObjectRef to_string_obj = JSValueToObject(ctx, to_string, NULL); 19 | JSValueRef obj_val = JSObjectCallAsFunction(ctx, to_string_obj, obj, 0, NULL, NULL); 20 | 21 | return JSValueToStringCopy(ctx, obj_val, NULL); 22 | } 23 | } 24 | 25 | void print_value(char *prefix, JSContextRef ctx, JSValueRef val) { 26 | if (val != NULL) { 27 | JSStringRef str = to_string(ctx, val); 28 | char *ex_str = value_to_c_string(ctx, JSValueMakeString(ctx, str)); 29 | fprintf(stderr, "%s%s\n", prefix, ex_str); 30 | free(ex_str); 31 | } 32 | } 33 | 34 | JSValueRef evaluate_script(JSContextRef ctx, char *script, char *source) { 35 | JSStringRef script_ref = JSStringCreateWithUTF8CString(script); 36 | JSStringRef source_ref = NULL; 37 | if (source != NULL) { 38 | source_ref = JSStringCreateWithUTF8CString(source); 39 | } 40 | 41 | JSValueRef ex = NULL; 42 | JSValueRef val = JSEvaluateScript(ctx, script_ref, NULL, source_ref, 0, &ex); 43 | JSStringRelease(script_ref); 44 | if (source != NULL) { 45 | JSStringRelease(source_ref); 46 | } 47 | 48 | // debug_print_value("evaluate_script", ctx, ex); 49 | 50 | return val; 51 | } 52 | 53 | char *value_to_c_string_ext(JSContextRef ctx, JSValueRef val, bool handle_non_string_values) { 54 | 55 | if (!handle_non_string_values && JSValueIsNull(ctx, val)) { 56 | return NULL; 57 | } 58 | 59 | if (!JSValueIsString(ctx, val)) { 60 | if (handle_non_string_values) { 61 | 62 | JSStringRef error_str = JSStringCreateWithUTF8CString("Error"); 63 | JSValueRef error_prop = JSObjectGetProperty(ctx, JSContextGetGlobalObject(ctx), error_str, NULL); 64 | JSObjectRef error_constructor_obj = JSValueToObject(ctx, error_prop, NULL); 65 | 66 | if (JSValueIsInstanceOfConstructor(ctx, val, error_constructor_obj, NULL)) { 67 | JSObjectRef error_obj = JSValueToObject(ctx, val, NULL); 68 | JSStringRef message_str = JSStringCreateWithUTF8CString("message"); 69 | JSValueRef message_prop = JSObjectGetProperty(ctx, error_obj, message_str, NULL); 70 | char* message = value_to_c_string(ctx, message_prop); 71 | JSStringRef stack_str = JSStringCreateWithUTF8CString("stack"); 72 | JSValueRef stack_prop = JSObjectGetProperty(ctx, error_obj, stack_str, NULL); 73 | char* stack = value_to_c_string(ctx, stack_prop); 74 | char* result = malloc(sizeof(char) * (strlen(message) + strlen(stack) + 2)); 75 | sprintf(result, "%s\n%s", message, stack); 76 | return result; 77 | } else { 78 | static JSObjectRef stringify_fn = NULL; 79 | 80 | if (!stringify_fn) { 81 | JSStringRef json_str = JSStringCreateWithUTF8CString("JSON"); 82 | JSValueRef json_prop = JSObjectGetProperty(ctx, JSContextGetGlobalObject(ctx), json_str, NULL); 83 | JSObjectRef json_obj = JSValueToObject(ctx, json_prop, NULL); 84 | JSStringRelease(json_str); 85 | JSStringRef stringify_str = JSStringCreateWithUTF8CString("stringify"); 86 | JSValueRef stringify_prop = JSObjectGetProperty(ctx, json_obj, stringify_str, NULL); 87 | JSStringRelease(stringify_str); 88 | stringify_fn = JSValueToObject(ctx, stringify_prop, NULL); 89 | JSValueProtect(ctx, stringify_fn); 90 | } 91 | 92 | size_t num_arguments = 3; 93 | JSValueRef arguments[num_arguments]; 94 | arguments[0] = val; 95 | arguments[1] = JSValueMakeNull(ctx); 96 | arguments[2] = c_string_to_value(ctx, " "); 97 | JSValueRef result = JSObjectCallAsFunction(ctx, stringify_fn, JSContextGetGlobalObject(ctx), 98 | num_arguments, arguments, NULL); 99 | 100 | return value_to_c_string(ctx, result); 101 | } 102 | } else { 103 | return NULL; 104 | } 105 | } 106 | 107 | JSStringRef str_ref = JSValueToStringCopy(ctx, val, NULL); 108 | size_t len = JSStringGetMaximumUTF8CStringSize(str_ref); 109 | char *str = malloc(len * sizeof(char)); 110 | JSStringGetUTF8CString(str_ref, str, len); 111 | JSStringRelease(str_ref); 112 | 113 | return str; 114 | } 115 | 116 | char *value_to_c_string(JSContextRef ctx, JSValueRef val) { 117 | return value_to_c_string_ext(ctx, val, false); 118 | } 119 | 120 | JSValueRef c_string_to_value(JSContextRef ctx, const char *s) { 121 | JSStringRef str = JSStringCreateWithUTF8CString(s); 122 | JSValueRef rv = JSValueMakeString(ctx, str); 123 | JSStringRelease(str); 124 | return rv; 125 | } 126 | 127 | int array_get_count(JSContextRef ctx, JSObjectRef arr) { 128 | JSStringRef pname = JSStringCreateWithUTF8CString("length"); 129 | JSValueRef val = JSObjectGetProperty(ctx, arr, pname, NULL); 130 | JSStringRelease(pname); 131 | return (int) JSValueToNumber(ctx, val, NULL); 132 | } 133 | -------------------------------------------------------------------------------- /planck-sh/plk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | function join { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; } 6 | 7 | # Extract opts 8 | print_classpath=false 9 | describe=false 10 | verbose=false 11 | force=false 12 | repro=false 13 | tree=false 14 | pom=false 15 | resolve_tags=false 16 | help=false 17 | resolve_aliases=() 18 | classpath_aliases=() 19 | main_aliases=() 20 | all_aliases=() 21 | while [ $# -gt 0 ] 22 | do 23 | case "$1" in 24 | -J*) 25 | shift 26 | ;; 27 | -R*) 28 | resolve_aliases+=("${1:2}") 29 | shift 30 | ;; 31 | -C*) 32 | classpath_aliases+=("${1:2}") 33 | shift 34 | ;; 35 | -O*) 36 | shift 37 | ;; 38 | -M*) 39 | main_aliases+=("${1:2}") 40 | shift 41 | ;; 42 | -A*) 43 | all_aliases+=("${1:2}") 44 | shift 45 | ;; 46 | -Sdeps) 47 | shift 48 | deps_data="${1}" 49 | shift 50 | ;; 51 | -Scp) 52 | shift 53 | force_cp="${1}" 54 | shift 55 | ;; 56 | -Spath) 57 | print_classpath=true 58 | shift 59 | ;; 60 | -Sverbose) 61 | verbose=true 62 | shift 63 | ;; 64 | -Sdescribe) 65 | describe=true 66 | shift 67 | ;; 68 | -Sforce) 69 | force=true 70 | shift 71 | ;; 72 | -Srepro) 73 | repro=true 74 | shift 75 | ;; 76 | -Stree) 77 | tree=true 78 | shift 79 | ;; 80 | -Spom) 81 | pom=true 82 | shift 83 | ;; 84 | -Sresolve-tags) 85 | resolve_tags=true 86 | shift 87 | ;; 88 | -S*) 89 | echo "Invalid option: $1" 90 | exit 1 91 | ;; 92 | -h|--help|"-?") 93 | if [[ ${#main_aliases[@]} -gt 0 ]] || [[ ${#all_aliases[@]} -gt 0 ]]; then 94 | break 95 | else 96 | help=true 97 | shift 98 | fi 99 | ;; 100 | *) 101 | break 102 | ;; 103 | esac 104 | done 105 | 106 | # Find clojure executable 107 | set +e 108 | CLOJURE_CMD=$(type -p clojure) 109 | set -e 110 | if [[ ! -n "$CLOJURE_CMD" ]]; then 111 | >&2 echo "Couldn't find 'clojure'." 112 | >&2 echo "You can launch Planck directly using 'planck'." 113 | >&2 echo "To use 'plk', please ensure 'clojure' is installed and on" 114 | >&2 echo "your path. See https://clojure.org/guides/getting_started" 115 | exit 1 116 | fi 117 | 118 | if "$help"; then 119 | cat <<-END 120 | Usage: plk [dep-opt*] [init-opt*] [main-opt] [arg*] 121 | 122 | The plk script is a runner for Planck which ultimately constructs and 123 | invokes a command-line of the form: 124 | 125 | planck --classpath classpath [init-opt*] [main-opt] [arg*] 126 | 127 | The dep-opts are used to build the classpath using the clojure tool: 128 | -Ralias... Concatenated resolve-deps aliases, ex: -R:bench:1.9 129 | -Calias... Concatenated make-classpath aliases, ex: -C:dev 130 | -Malias... Concatenated main option aliases, ex: -M:test 131 | -Aalias... Concatenated aliases of any kind, ex: -A:dev:mem 132 | -Sdeps EDN Deps data to use as the final deps file 133 | -Spath Compute classpath and echo to stdout only 134 | -Scp CP Do NOT compute or cache classpath, use this one instead 135 | -Srepro Ignore the ~/.clojure/deps.edn config file 136 | -Sforce Force recomputation of the classpath (don't use the cache) 137 | -Spom Generate (or update existing) pom.xml with deps and paths 138 | -Stree Print dependency tree 139 | -Sresolve-tags Resolve git coordinate tags to shas and update deps.edn 140 | -Sverbose Print important path info to console 141 | -Sdescribe Print environment and command parsing info as data 142 | 143 | Additionally, for compatibility with clojure, -Jopt and -Oalias... dep-opts 144 | are accepted but ignored. 145 | END 146 | planck -h | tail -n +6 147 | exit 0 148 | fi 149 | 150 | # Execute resolve-tags command 151 | if "$resolve_tags"; then 152 | "$CLOJURE_CMD" -Sresolve-tags 153 | exit 154 | fi 155 | 156 | clojure_args=() 157 | if [[ -n "$deps_data" ]]; then 158 | clojure_args+=("-Sdeps" "$deps_data") 159 | fi 160 | if [[ ${#resolve_aliases[@]} -gt 0 ]]; then 161 | clojure_args+=("-R$(join '' ${resolve_aliases[@]})") 162 | fi 163 | if [[ ${#classpath_aliases[@]} -gt 0 ]]; then 164 | clojure_args+=("-C$(join '' ${classpath_aliases[@]})") 165 | fi 166 | if [[ ${#main_aliases[@]} -gt 0 ]]; then 167 | clojure_args+=("-M$(join '' ${main_aliases[@]})") 168 | fi 169 | if [[ ${#all_aliases[@]} -gt 0 ]]; then 170 | clojure_args+=("-A$(join '' ${all_aliases[@]})") 171 | fi 172 | if "$repro"; then 173 | clojure_args+=("-Srepro") 174 | fi 175 | if "$force"; then 176 | clojure_args+=("-Sforce") 177 | fi 178 | 179 | if "$pom"; then 180 | if "$verbose"; then 181 | clojure_args+=("-Sverbose") 182 | fi 183 | "$CLOJURE_CMD" "${clojure_args[@]}" -Spom 184 | elif "$describe"; then 185 | if "$verbose"; then 186 | clojure_args+=("-Sverbose") 187 | fi 188 | "$CLOJURE_CMD" "${clojure_args[@]}" -Sdescribe 189 | elif "$tree"; then 190 | if "$verbose"; then 191 | clojure_args+=("-Sverbose") 192 | fi 193 | "$CLOJURE_CMD" "${clojure_args[@]}" -Stree 194 | else 195 | set -f 196 | if [[ -n "$force_cp" ]]; then 197 | cp="$force_cp" 198 | else 199 | if "$verbose"; then 200 | "$CLOJURE_CMD" "${clojure_args[@]}" -Sverbose -e nil 201 | fi 202 | cp=`"$CLOJURE_CMD" "${clojure_args[@]}" -Spath` 203 | fi 204 | if "$print_classpath"; then 205 | echo $cp 206 | else 207 | if [[ ${#main_aliases[@]} -gt 0 ]] || [[ ${#all_aliases[@]} -gt 0 ]]; then 208 | # Attempt to extract the main cache filename by parsing the output of -Sverbose 209 | cp_file=`"$CLOJURE_CMD" "${clojure_args[@]}" -Sverbose -Spath | grep cp_file | cut -d = -f 2 | sed 's/^ *//g'` 210 | main_file="${cp_file%.cp}.main" 211 | fi 212 | if [[ -e "$main_file" ]]; then 213 | main_cache_opts=($(cat "$main_file")) 214 | fi 215 | exec planck --classpath "$cp" "${main_cache_opts[@]}" "$@" 216 | fi 217 | fi 218 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Planck 2 | 3 | A stand-alone ClojureScript REPL for macOS and Linux based on JavaScriptCore. 4 | 5 | Home page: [planck-repl.org](https://planck-repl.org) 6 | 7 | # Installing 8 | 9 | On macOS: 10 | 11 | ```shell 12 | brew install planck 13 | ``` 14 | 15 | On Ubuntu: 16 | 17 | ```shell 18 | sudo add-apt-repository ppa:mfikes/planck 19 | sudo apt-get update 20 | sudo apt-get install planck 21 | ``` 22 | 23 | For other Linux distros, [download](https://planck-repl.org/binaries/) a binary or see [Building](https://github.com/planck-repl/planck#building) below. 24 | 25 | # Using 26 | 27 | [![cljdoc badge](https://cljdoc.org/badge/planck/planck)](https://cljdoc.org/d/planck/planck/CURRENT) 28 | 29 | Launch Planck by entering `planck` or `plk` at the terminal. 30 | 31 | > The `plk` script executes `planck`, while integrating with the [`clojure`](https://clojure.org/guides/getting_started) CLI tool to add support for `deps.edn` and classpath-affecting options such as `-Aalias`. 32 | 33 | Get help on command-line options by issuing `planck -h` or `plk -h`. 34 | 35 | ### Ported Clojure Functionality 36 | 37 | It is possible to write Clojure-idiomatic scripts like the following: 38 | 39 | ```clojure 40 | (require '[planck.core :refer [line-seq with-open]] 41 | '[planck.io :as io] 42 | '[planck.shell :as shell]) 43 | 44 | (with-open [rdr (io/reader "input.txt")] 45 | (doseq [line (line-seq rdr)] 46 | (println (count line)))) 47 | 48 | (shell/sh "say" "done") 49 | ``` 50 | 51 | Many of the familiar functions and macros unique to Clojure have been ported: 52 | 53 | #### clojure.core/ -> planck.core/ 54 | 55 | [file-seq](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#file-seq), 56 | [find-var](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#find-var), 57 | [load-reader](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#load-reader), 58 | [load-string](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#load-string), 59 | [line-seq](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#line-seq), 60 | [intern](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#intern), 61 | [ns-aliases](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#ns-aliases), 62 | [ns-refers](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#ns-refers), 63 | [ns-resolve](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#ns-resolve), 64 | [read](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#read), 65 | [read-line](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#read-line), 66 | [read-string](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#read-string), 67 | [resolve](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#resolve), 68 | [slurp](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#slurp), 69 | [spit](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#spit), 70 | [with-in-str](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#with-in-str), 71 | [with-open](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.core#with-open) 72 | 73 | #### clojure.java.io/ -> planck.io/ 74 | 75 | [as-file](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#as-file), 76 | [as-relative-path](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#as-relative-path), 77 | [as-url](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#as-url), 78 | [delete-file](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#delete-file), 79 | [file](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#file), 80 | [input-stream](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#input-stream), 81 | [make-input-stream](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#make-input-stream), 82 | [make-output-stream](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#make-output-stream), 83 | [make-parents](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#make-parents), 84 | [make-reader](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#make-reader), 85 | [make-writer](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#make-writer), 86 | [output-stream](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#output-stream), 87 | [reader](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#reader), 88 | [resource](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#resource), 89 | [writer](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.io#writer) 90 | 91 | #### clojure.java.shell/ -> planck.shell/ 92 | 93 | [sh](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.shell#sh), 94 | [with-sh-dir](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.shell#with-sh-dir), 95 | [with-sh-env](https://cljdoc.org/d/planck/planck/CURRENT/api/planck.shell#with-sh-env) 96 | 97 | # Building 98 | 99 | If using macOS or Ubuntu, you can install pre-built binaries as described above under "Installing". The instructions here can be used to build, test, and optionally install Planck on your machine. 100 | 101 | ## Prerequisites 102 | 103 | See [Building Wiki](https://github.com/planck-repl/planck/wiki/Building) for setting up OS-specific build tooling and dependencies. 104 | 105 | Pre-made build environments for various environments are available in [build-envs](https://github.com/planck-repl/build-envs). 106 | 107 | ## Compiling 108 | 109 | ```shell 110 | script/build 111 | ``` 112 | 113 | The resulting binary will be `planck-c/build/planck`. 114 | 115 | Specify `--fast` to quickly build a development version that skips Closure optimization: 116 | 117 | ```shell 118 | script/build --fast 119 | ``` 120 | 121 | If you specify `-Sdeps` or `-R`, it will be passed through to the underlying [`clojure`](https://clojure.org/guides/deps_and_cli) command during the build process. This can be used to specify a ClojureScript dep to use. 122 | 123 | ## Tests 124 | 125 | ```shell 126 | script/test 127 | ``` 128 | 129 | ## Installing 130 | 131 | The following will install Planck under the prefix `/usr/local`: 132 | 133 | ```shell 134 | sudo script/install 135 | ``` 136 | 137 | If you'd like to install Planck under a different prefix, you may pass `-p`. For example: 138 | 139 | ```shell 140 | sudo script/install -p /usr 141 | ``` 142 | 143 | # License 144 | 145 | Planck™ copyright © 2015–2024 Mike Fikes and Contributors 146 | 147 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 148 | -------------------------------------------------------------------------------- /planck-c/io.c: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | #include "availability.h" 3 | #ifdef __MAC_OS_X_VERSION_MIN_REQUIRED 4 | #if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 5 | #define PLANCK_USE_CLONEFILE 1 6 | #endif 7 | #endif 8 | #endif 9 | 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifdef PLANCK_USE_CLONEFILE 22 | #include 23 | #include 24 | #include "engine.h" 25 | #endif 26 | 27 | #define CHUNK_SIZE 1024 28 | 29 | char *read_all(FILE *f) { 30 | int len = CHUNK_SIZE + 1; 31 | char *buf = malloc(len * sizeof(char)); 32 | 33 | size_t offset = 0; 34 | for (;;) { 35 | if (len - offset < CHUNK_SIZE) { 36 | len = 2 * len + CHUNK_SIZE; 37 | buf = realloc(buf, len * sizeof(char)); 38 | } 39 | size_t n = fread(buf + offset, 1, CHUNK_SIZE, f); 40 | offset += n; 41 | if (feof(f)) { 42 | break; 43 | } 44 | if (ferror(f)) { 45 | return NULL; 46 | } 47 | } 48 | memset(buf + offset, 0, len - offset); 49 | return buf; 50 | } 51 | 52 | char *get_contents(char *path, time_t *last_modified) { 53 | FILE *f = fopen(path, "r"); 54 | if (f == NULL) { 55 | goto err; 56 | } 57 | 58 | struct stat f_stat; 59 | if (fstat(fileno(f), &f_stat) < 0) { 60 | goto err; 61 | } 62 | 63 | if (last_modified != NULL) { 64 | *last_modified = f_stat.st_mtime; 65 | } 66 | 67 | char *buf = malloc(f_stat.st_size + 1); 68 | memset(buf, 0, f_stat.st_size); 69 | size_t n = fread(buf, f_stat.st_size, 1, f); 70 | if (n != 1) { 71 | free(buf); 72 | goto err; 73 | } 74 | buf[f_stat.st_size] = '\0'; 75 | if (ferror(f)) { 76 | free(buf); 77 | goto err; 78 | } 79 | 80 | if (fclose(f) < 0) { 81 | free(buf); 82 | goto err; 83 | } 84 | 85 | return buf; 86 | 87 | err: 88 | return NULL; 89 | } 90 | 91 | void write_contents(char *path, char *contents) { 92 | FILE *f = fopen(path, "w"); 93 | if (f == NULL) { 94 | return; 95 | } 96 | 97 | size_t len = strlen(contents); 98 | int offset = 0; 99 | do { 100 | int res = fwrite(contents + offset, 1, len - offset, f); 101 | if (res < 0) { 102 | return; 103 | } 104 | offset += res; 105 | } while (offset < len); 106 | 107 | if (fclose(f) < 0) { 108 | return; 109 | } 110 | 111 | return; 112 | } 113 | 114 | int mkdir_p(char *path) { 115 | int res = mkdir(path, 0755); 116 | if (res < 0 && errno == EEXIST) { 117 | return 0; 118 | } 119 | return res; 120 | } 121 | 122 | int mkdir_parents(const char *path) { 123 | /* Adapted from http://stackoverflow.com/a/2336245/119527 */ 124 | const size_t len = strlen(path); 125 | char _path[PATH_MAX]; 126 | char *p; 127 | 128 | errno = 0; 129 | 130 | /* Copy string so its mutable */ 131 | if (len > sizeof(_path) - 1) { 132 | errno = ENAMETOOLONG; 133 | return -1; 134 | } 135 | strcpy(_path, path); 136 | 137 | /* Iterate the string */ 138 | for (p = _path + 1; *p; p++) { 139 | if (*p == '/') { 140 | /* Temporarily truncate */ 141 | *p = '\0'; 142 | 143 | if (mkdir(_path, S_IRWXU) != 0) { 144 | if (errno != EEXIST) 145 | return -1; 146 | } 147 | 148 | *p = '/'; 149 | } 150 | } 151 | 152 | if (mkdir(_path, S_IRWXU) != 0) { 153 | if (errno != EEXIST) 154 | return -1; 155 | } 156 | 157 | return 0; 158 | } 159 | 160 | int copy_file_loop(const char *from, const char *to) { 161 | int fd_to, fd_from; 162 | char buf[4096]; 163 | ssize_t nread; 164 | int saved_errno; 165 | 166 | fd_from = open(from, O_RDONLY); 167 | if (fd_from < 0) 168 | return -1; 169 | 170 | fd_to = open(to, O_WRONLY | O_CREAT | O_EXCL, 0666); 171 | if (fd_to < 0) 172 | goto out_error; 173 | 174 | while (nread = read(fd_from, buf, sizeof buf), nread > 0) { 175 | char *out_ptr = buf; 176 | ssize_t nwritten; 177 | 178 | do { 179 | nwritten = write(fd_to, out_ptr, (size_t) nread); 180 | 181 | if (nwritten >= 0) { 182 | nread -= nwritten; 183 | out_ptr += nwritten; 184 | } else if (errno != EINTR) { 185 | goto out_error; 186 | } 187 | } while (nread > 0); 188 | } 189 | 190 | if (nread == 0) { 191 | if (close(fd_to) < 0) { 192 | fd_to = -1; 193 | goto out_error; 194 | } 195 | close(fd_from); 196 | 197 | /* Success! */ 198 | return 0; 199 | } 200 | 201 | out_error: 202 | saved_errno = errno; 203 | 204 | close(fd_from); 205 | if (fd_to >= 0) 206 | close(fd_to); 207 | 208 | errno = saved_errno; 209 | return -1; 210 | } 211 | 212 | int copy_file_loop_unlinking(const char *from, const char *to) { 213 | if (-1 == copy_file_loop(from, to)) { 214 | if (EEXIST == errno) { 215 | if (-1 == unlink(to)) { 216 | return -1; 217 | } else { 218 | return copy_file_loop(from, to); 219 | } 220 | } else { 221 | return -1; 222 | } 223 | } else { 224 | return 0; 225 | } 226 | } 227 | 228 | int copy_file(const char *from, const char *to) { 229 | 230 | #ifdef PLANCK_USE_CLONEFILE 231 | if (-1 == clonefile(from, to, 0)) { 232 | if (EEXIST == errno) { 233 | if (-1 == unlink(to)) { 234 | return -1; 235 | } else { 236 | if (-1 == clonefile(from, to, 0)) { 237 | return copy_file_loop_unlinking(from, to); 238 | } else { 239 | return 0; 240 | } 241 | } 242 | } else { 243 | return copy_file_loop_unlinking(from, to); 244 | } 245 | } else { 246 | return 0; 247 | } 248 | #else 249 | return copy_file_loop_unlinking(from, to); 250 | #endif 251 | 252 | } 253 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/repl_resources.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:no-doc planck.repl-resources 2 | "Resources for use in the Planck REPL implementation.") 3 | 4 | (def special-doc-map 5 | '{. {:forms [(.instanceMethod instance args*) 6 | (.-instanceField instance)] 7 | :doc "The instance member form works for methods and fields. 8 | They all expand into calls to the dot operator at macroexpansion time."} 9 | ns {:forms [(name docstring? attr-map? references*)] 10 | :doc "You must currently use the ns form only with the following caveats 11 | 12 | * You must use the :only form of :use 13 | * :require supports :as, :refer, and :rename 14 | - all options can be skipped 15 | - in this case a symbol can be used as a libspec directly 16 | - that is, (:require lib.foo) and (:require [lib.foo]) are both 17 | supported and mean the same thing 18 | - :rename specifies a map from referred var names to different 19 | symbols (and can be used to prevent clashes) 20 | - prefix lists are not supported 21 | * The only options for :refer-clojure are :exclude and :rename 22 | * :import is available for importing Google Closure classes 23 | - ClojureScript types and records should be brought in with :use 24 | or :require :refer, not :import ed 25 | * Macros must be defined in a different compilation stage than the one 26 | from where they are consumed. One way to achieve this is to define 27 | them in one namespace and use them from another. They are referenced 28 | via the :require-macros / :use-macros options to ns 29 | - :require-macros and :use-macros support the same forms that 30 | :require and :use do 31 | 32 | Implicit macro loading: If a namespace is required or used, and that 33 | namespace itself requires or uses macros from its own namespace, then 34 | the macros will be implicitly required or used using the same 35 | specifications. Furthermore, in this case, macro vars may be included 36 | in a :refer or :only spec. This oftentimes leads to simplified library 37 | usage, such that the consuming namespace need not be concerned about 38 | explicitly distinguishing between whether certain vars are functions 39 | or macros. For example: 40 | 41 | (ns testme.core (:require [cljs.test :as test :refer [test-var deftest]])) 42 | 43 | will result in test/is resolving properly, along with the test-var 44 | function and the deftest macro being available unqualified. 45 | 46 | Inline macro specification: As a convenience, :require can be given 47 | either :include-macros true or :refer-macros [syms...]. Both desugar 48 | into forms which explicitly load the matching Clojure file containing 49 | macros. (This works independently of whether the namespace being 50 | required internally requires or uses its own macros.) For example: 51 | 52 | (ns testme.core 53 | (:require [foo.core :as foo :refer [foo-fn] :include-macros true] 54 | [woz.core :as woz :refer [woz-fn] :refer-macros [app jx]])) 55 | 56 | is sugar for 57 | 58 | (ns testme.core 59 | (:require [foo.core :as foo :refer [foo-fn]] 60 | [woz.core :as woz :refer [woz-fn]]) 61 | (:require-macros [foo.core :as foo] 62 | [woz.core :as woz :refer [app jx]])) 63 | 64 | Auto-aliasing clojure namespaces: If a non-existing clojure.* namespace 65 | is required or used and a matching cljs.* namespace exists, the cljs.* 66 | namespace will be loaded and an alias will be automatically established 67 | from the clojure.* namespace to the cljs.* namespace. For example: 68 | 69 | (ns testme.core (:require [clojure.test])) 70 | 71 | will be automatically converted to 72 | 73 | (ns testme.core (:require [cljs.test :as clojure.test]))"} 74 | def {:forms [(def symbol doc-string? init?)] 75 | :doc "Creates and interns a global var with the name 76 | of symbol in the current namespace (*ns*) or locates such a var if 77 | it already exists. If init is supplied, it is evaluated, and the 78 | root binding of the var is set to the resulting value. If init is 79 | not supplied, the root binding of the var is unaffected."} 80 | do {:forms [(do exprs*)] 81 | :doc "Evaluates the expressions in order and returns the value of 82 | the last. If no expressions are supplied, returns nil."} 83 | if {:forms [(if test then else?)] 84 | :doc "Evaluates test. If not the singular values nil or false, 85 | evaluates and yields then, otherwise, evaluates and yields else. If 86 | else is not supplied it defaults to nil."} 87 | new {:forms [(Constructor. args*) (new Constructor args*)] 88 | :url "java_interop#new" 89 | :doc "The args, if any, are evaluated from left to right, and 90 | passed to the JavaScript constructor. The constructed object is 91 | returned."} 92 | quote {:forms [(quote form)] 93 | :doc "Yields the unevaluated form."} 94 | recur {:forms [(recur exprs*)] 95 | :doc "Evaluates the exprs in order, then, in parallel, rebinds 96 | the bindings of the recursion point to the values of the exprs. 97 | Execution then jumps back to the recursion point, a loop or fn method."} 98 | set! {:forms [(set! var-symbol expr) 99 | (set! (.- instance-expr instanceFieldName-symbol) expr)] 100 | :url "vars#set" 101 | :doc "Used to set vars and JavaScript object fields"} 102 | throw {:forms [(throw expr)] 103 | :doc "The expr is evaluated and thrown."} 104 | try {:forms [(try expr* catch-clause* finally-clause?)] 105 | :doc "catch-clause => (catch classname name expr*) 106 | finally-clause => (finally expr*) 107 | Catches and handles JavaScript exceptions."} 108 | var {:forms [(var symbol)] 109 | :doc "The symbol must resolve to a var, and the Var object 110 | itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) 111 | 112 | (def repl-special-doc-map 113 | '{in-ns {:arglists ([name]) 114 | :doc "Sets *cljs-ns* to the namespace named by the symbol, creating it if needed."} 115 | load-file {:arglists ([name]) 116 | :doc "Sequentially read and evaluate the set of forms contained in the file."} 117 | load {:arglists ([& paths]) 118 | :doc "Loads Clojure code from resources in classpath. A path is interpreted as 119 | classpath-relative if it begins with a slash or relative to the root 120 | directory for the current namespace otherwise."}}) 121 | -------------------------------------------------------------------------------- /planck-cljs/src/planck/from/cljs_bean/from/cljs/core.cljs: -------------------------------------------------------------------------------- 1 | ; Copyright (c) Rich Hickey. All rights reserved. 2 | ; The use and distribution terms for this software are covered by the 3 | ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 4 | ; which can be found in the file epl-v10.html at the root of this distribution. 5 | ; By using this software in any fashion, you are agreeing to be bound by 6 | ; the terms of this license. 7 | ; You must not remove this notice, or any other, from this software. 8 | 9 | (ns ^:no-doc planck.from.cljs-bean.from.cljs.core) 10 | 11 | ;; Copied and made public, adding ^not-native hints 12 | (defn -indexOf 13 | ([^not-native coll x] 14 | (-indexOf coll x 0)) 15 | ([^not-native coll x start] 16 | (let [len (count coll)] 17 | (if (>= start len) 18 | -1 19 | (loop [idx (cond 20 | (pos? start) start 21 | (neg? start) (max 0 (+ start len)) 22 | :else start)] 23 | (if (< idx len) 24 | (if (= (nth coll idx) x) 25 | idx 26 | (recur (inc idx))) 27 | -1)))))) 28 | 29 | ;; Copied and made public, adding ^not-native hints 30 | (defn -lastIndexOf 31 | ([^not-native coll x] 32 | (-lastIndexOf coll x (count coll))) 33 | ([^not-native coll x start] 34 | (let [len (count coll)] 35 | (if (zero? len) 36 | -1 37 | (loop [idx (cond 38 | (pos? start) (min (dec len) start) 39 | (neg? start) (+ len start) 40 | :else start)] 41 | (if (>= idx 0) 42 | (if (= (nth coll idx) x) 43 | idx 44 | (recur (dec idx))) 45 | -1)))))) 46 | 47 | ;; Copied and made public, adding ^not-native hints 48 | (defn compare-indexed 49 | "Compare indexed collection." 50 | ([^not-native xs ys] 51 | (let [xl (count xs) 52 | yl (count ys)] 53 | (cond 54 | (< xl yl) -1 55 | (> xl yl) 1 56 | (== xl 0) 0 57 | :else (compare-indexed xs ys xl 0)))) 58 | ([^not-native xs ys len n] 59 | (let [d (compare (nth xs n) (nth ys n))] 60 | (if (and (zero? d) (< (+ n 1) len)) 61 | (recur xs ys len (inc n)) 62 | d)))) 63 | 64 | ;; Copied and made public, adding ^not-native hint 65 | (defn equiv-sequential 66 | "Assumes x is sequential. Returns true if x equals y, otherwise 67 | returns false." 68 | [^not-native x y] 69 | (boolean 70 | (when (sequential? y) 71 | (if (and (counted? x) (counted? y) 72 | (not (== (count x) (count y)))) 73 | false 74 | (loop [xs (seq x) ys (seq y)] 75 | (cond (nil? xs) (nil? ys) 76 | (nil? ys) false 77 | (= (first xs) (first ys)) (recur (next xs) (next ys)) 78 | :else false)))))) 79 | 80 | ;; Copied and made public, adding ^not-native hints 81 | (defn ci-reduce 82 | "Accepts any collection which satisfies the ICount and IIndexed protocols and 83 | reduces them without incurring seq initialization" 84 | ([^not-native cicoll f] 85 | (let [cnt (-count cicoll)] 86 | (if (zero? cnt) 87 | (f) 88 | (loop [val (-nth cicoll 0), n 1] 89 | (if (< n cnt) 90 | (let [nval (f val (-nth cicoll n))] 91 | (if (reduced? nval) 92 | @nval 93 | (recur nval (inc n)))) 94 | val))))) 95 | ([^not-native cicoll f val] 96 | (let [cnt (-count cicoll)] 97 | (loop [val val, n 0] 98 | (if (< n cnt) 99 | (let [nval (f val (-nth cicoll n))] 100 | (if (reduced? nval) 101 | @nval 102 | (recur nval (inc n)))) 103 | val)))) 104 | ([^not-native cicoll f val idx] 105 | (let [cnt (-count cicoll)] 106 | (loop [val val, n idx] 107 | (if (< n cnt) 108 | (let [nval (f val (-nth cicoll n))] 109 | (if (reduced? nval) 110 | @nval 111 | (recur nval (inc n)))) 112 | val))))) 113 | 114 | ;; Copied from TransientArrayMap and modified with editable? param, adding ^not-native hint 115 | (defn TransientArrayMap-conj! [^not-native tcoll o editable?] 116 | (if editable? 117 | (cond 118 | (map-entry? o) 119 | (-assoc! tcoll (key o) (val o)) 120 | 121 | (vector? o) 122 | (-assoc! tcoll (o 0) (o 1)) 123 | 124 | :else 125 | (loop [es (seq o) tcoll tcoll] 126 | (if-let [e (first es)] 127 | (recur (next es) 128 | (-assoc! tcoll (key e) (val e))) 129 | tcoll))) 130 | (throw (js/Error. "conj! after persistent!")))) 131 | 132 | ;; Copied from PersistentArrayMap, adding ^not-native hint 133 | (defn PersistentArrayMap-conj [^not-native coll entry] 134 | (if (vector? entry) 135 | (-assoc coll (-nth entry 0) (-nth entry 1)) 136 | (loop [ret coll es (seq entry)] 137 | (if (nil? es) 138 | ret 139 | (let [e (first es)] 140 | (if (vector? e) 141 | (recur (-assoc ret (-nth e 0) (-nth e 1)) 142 | (next es)) 143 | (throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))) 144 | 145 | ;; Copied from TransientVector and parameterized on type-name, adding ^not-native hint 146 | (defn TransientVector-assoc! [^not-native tcoll key val type-name] 147 | (if (number? key) 148 | (-assoc-n! tcoll key val) 149 | (throw (js/Error. (str type-name "'s key for assoc! must be a number."))))) 150 | 151 | ;; Copied from PersistentVector and parameterized on type and cnt, adding ^not-native hint 152 | (defn PersistentVector-equiv [^not-native coll other type cnt] 153 | (if (instance? type other) 154 | (if (== cnt (count other)) 155 | (let [me-iter (-iterator coll) 156 | you-iter (-iterator other)] 157 | (loop [] 158 | (if ^boolean (.hasNext me-iter) 159 | (let [x (.next me-iter) 160 | y (.next you-iter)] 161 | (if (= x y) 162 | (recur) 163 | false)) 164 | true))) 165 | false) 166 | (equiv-sequential coll other))) 167 | 168 | ;; Copied from PersistentVector, adding ^not-native hint 169 | (defn PersistentVector-lookup [^not-native coll k not-found] 170 | (if (number? k) 171 | (-nth coll k not-found) 172 | not-found)) 173 | 174 | ;; Copied from PersistentVector, adding ^not-native hint 175 | (defn PersistentVector-assoc [^not-native coll k v] 176 | (if (number? k) 177 | (-assoc-n coll k v) 178 | (throw (js/Error. "Vector's key for assoc must be a number.")))) 179 | 180 | ;; Copied from PersistentVector an parameterized on cnt 181 | (defn PersistentVector-contains-key? [coll k cnt] 182 | (if (integer? k) 183 | (and (<= 0 k) (< k cnt)) 184 | false)) 185 | -------------------------------------------------------------------------------- /doc/scripts.md: -------------------------------------------------------------------------------- 1 | ## Scripts 2 | 3 | 4 | Planck can be used to run scripts written in ClojureScript. Planck and JavaScriptCore are fast to start up, and the ClojureScript reader and compiler have been optimized for bootstrapped mode, making this a perfectly feasible approach. It makes for a great alternative for shell scripts written in, say, Bash. 5 | 6 | Perhaps the simplest way to execute a script with Planck is to create a file and to use `plk` or `planck` to run it. For example, say you have `foo.cljs` with 7 | 8 | ``` 9 | (println "Hello World!") 10 | ``` 11 | 12 | Then you can execute it: 13 | 14 | ``` 15 | $ plk foo.cljs 16 | Hello World! 17 | ``` 18 | 19 | ### Standalone Scripts 20 | 21 | What if you'd like to make a standalone executable? The Clojure reader treats `#!` as a line comment, supporting the use of shebang scripts. You can change `foo.cljs` to look like 22 | 23 | ``` 24 | #!/usr/bin/env plk 25 | (println "Hello World!") 26 | ``` 27 | 28 | and then if you first set the executable bit, you can execute the file directly: 29 | 30 | ``` 31 | $ chmod +x foo.cljs 32 | $ ./foo.cljs 33 | Hello World! 34 | ``` 35 | 36 | ``` 37 | $ plk bar.cljs there 38 | Hello there! 39 | ``` 40 | 41 | > If you'd like to directly specify dependencies in scripts using `#!` see the “Shebang Deps” section of [Dependencies](dependencies.md). 42 | 43 | ### Main Function 44 | 45 | #### Specifying the Main Namespace 46 | 47 | If you'd like your script to start execution by executing a main function, you can make use of Planck's `-m` command-line option, specifying the namespace containing a `-main` function. Let's say you have `foo/core.cljs` with: 48 | 49 | ``` 50 | (ns foo.core) 51 | 52 | (defn greet [name] 53 | (println (str "Hello " name "!"))) 54 | 55 | (defn -main [name] 56 | (greet name)) 57 | ``` 58 | 59 | then this works: 60 | 61 | ``` 62 | $ plk -m foo.core ClojureScript 63 | Hello ClojureScript! 64 | ``` 65 | 66 | #### Specifying the Main Function 67 | 68 | Alternatively, you can make use of `cljs.core/*main-cli-fn*`. If this Var is set to a function, and `-m` hasn't been specified, then the main function will be called. 69 | 70 | This can be especially useful for standalone scripts on Linux, where it is not possible to specify interpreter arguments in the shebang line. Consider this alternative to the above, where this file is saved as `foo`: 71 | 72 | ``` 73 | #!/usr/bin/env plk 74 | (ns foo.core) 75 | 76 | (defn greet [name] 77 | (println (str "Hello " name "!"))) 78 | 79 | (defn -main [name] 80 | (greet name)) 81 | 82 | (set! *main-cli-fn* -main) 83 | ``` 84 | 85 | Then this works: 86 | 87 | ``` 88 | $ ./foo ClojureScript 89 | Hello ClojureScript! 90 | ``` 91 | 92 | All that needs to be ensured is that the code that `set!`s `*main-cli-fn*` is called, either via a `-e` to require the needed namespace, or by direct execution as in the example above. 93 | 94 | ### Interacting with standard input and output 95 | 96 | When writing scripts, getting input from standard input is quite useful. Clojure 97 | has two dynamic vars, `core/*in*` and `core/*out*`. Whenever you use `println`, 98 | you're actually printing to whatever `core/*out*` is bound to. ClojureScript has 99 | its own `*out*` which lives in `cljs.core`, but it lacks (for obvious reaons) 100 | `*in*`. Planck, though provides `planck.core/*in*` which lets you interact with 101 | standard input. 102 | 103 | So in order to demonstrate this, here is a script that simply copies each line 104 | it receives on standard input to standard out: 105 | 106 | ``` 107 | #!/usr/bin/env plk 108 | 109 | (ns example.echo 110 | (:require [planck.core :as core])) 111 | 112 | (doseq [l (core/line-seq core/*in*)] 113 | (println l)) 114 | ``` 115 | 116 | ### Command Line Arguments 117 | 118 | If you'd like to gain access to the command line arguments passed to your script, they are available in `cljs.core/*command-line-args*`. 119 | 120 | With `bar.cljs`: 121 | 122 | ``` 123 | (ns bar.core) 124 | 125 | (println (str "Hello " (first *command-line-args*) "!")) 126 | ``` 127 | 128 | #### Argument Processing 129 | 130 | You can use the [`clojure.tools.cli`](https://github.com/clojure/tools.cli) library to parse command line options. Here is the intro example being used with Planck: 131 | 132 | ``` 133 | $ plk -Sdeps '{:deps {org.clojure/tools.cli {:mvn/version "0.3.5"}}}' -m my.program -vvvp8080 foo --help --invalid-opt 134 | {:options {:port 8080, :verbosity 3, :help true}, 135 | :arguments ["foo"], 136 | :summary " -p, --port PORT 80 Port number\n -v Verbosity level\n -h, --help", 137 | :errors ["Unknown option: \"--invalid-opt\""]} 138 | ``` 139 | 140 | where `src/my/program.cljs` contains: 141 | 142 | ```clojure 143 | (ns my.program 144 | (:require [clojure.tools.cli :refer [parse-opts]] 145 | [fipp.edn :refer [pprint]])) 146 | 147 | (def cli-options 148 | ;; An option with a required argument 149 | [["-p" "--port PORT" "Port number" 150 | :default 80 151 | :parse-fn #(js/parseInt %) 152 | :validate [#(< 0 % 0x10000) "Must be a number between 0 and 65536"]] 153 | ;; A non-idempotent option 154 | ["-v" nil "Verbosity level" 155 | :id :verbosity 156 | :default 0 157 | :assoc-fn (fn [m k _] (update-in m [k] inc))] 158 | ;; A boolean option defaulting to nil 159 | ["-h" "--help"]]) 160 | 161 | (defn -main [& args] 162 | (pprint (parse-opts args cli-options))) 163 | ``` 164 | 165 | ### Environment Variables 166 | 167 | Environment variables are accessible via `planck.environ/env`. For example, the following script will print the `HOME` environment variable: 168 | 169 | ``` 170 | (ns baz.core 171 | (:require [planck.environ :refer [env]])) 172 | 173 | (println (:home env)) 174 | ``` 175 | 176 | ### Shell Interaction 177 | 178 | The `planck.shell` namespace provides functions for interacting with the shell. 179 | Commands can be executed by running the `sh` function as seen in the following example: 180 | 181 | ``` 182 | #!/usr/bin/env plk 183 | (ns foo.core 184 | (:require [planck.shell :refer [sh]])) 185 | 186 | (defn list-files [dir] 187 | (println "listing files in" dir) 188 | (println (sh "ls" "-l" dir))) 189 | 190 | (list-files (first *command-line-args*)) 191 | ``` 192 | 193 | ### Script Termination Delay 194 | 195 | If you run a script that starts a timer, or launches an asynchronous shell interaction, the script will continue running so long as there are pending timers or shell activities. 196 | 197 | For example, this script will run for 5 seconds, print "done" and then terminate: 198 | 199 | ``` 200 | (def x (js/setTimeout #(println "hi") 1e6)) 201 | 202 | (js/setTimeout #(println "done") 5000) 203 | 204 | (js/clearTimeout x) 205 | ``` 206 | 207 | Similarly, this script will wait for 3 seconds, print `:done` and terminate: 208 | 209 | ``` 210 | (require '[planck.shell :refer [sh-async]]) 211 | 212 | (sh-async "sleep" "3" #(prn :done)) 213 | ``` 214 | 215 | 216 | 217 | 218 | --------------------------------------------------------------------------------