Skip to content
Open
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
87a2591
proof of concept for attaching :doc metadata to genarte functions
behrica May 7, 2024
07b6189
extrcated help into own ns to avoid repetion and cyclic dependencies
behrica May 8, 2024
4b4d162
aaded a global flag (default off), which controls if help is attached
behrica May 8, 2024
66feb0f
refcatored and added tests
behrica May 8, 2024
0c78839
Merge branch 'master' of https://github.com/scicloj/clojisr into help…
behrica May 8, 2024
0a1bf37
moved test
behrica May 8, 2024
42ad811
made tests more robust
behrica May 8, 2024
ec67fc7
make memoize work
behrica May 8, 2024
0df3e68
fixed syntax error in Dockerfile
behrica May 8, 2024
5a830cb
fixed apt get
behrica May 8, 2024
b74e11d
updated jsd version to fix CI build
behrica May 11, 2024
8d38dc0
postpone help attaching va future
behrica May 11, 2024
09d129f
fixed tests
behrica May 11, 2024
3ea82e6
refactored help->:doc and use alter-meta! to set it in a future, always
behrica May 12, 2024
435da76
commented one failing test , as :doc is now present on vars
behrica May 12, 2024
55cebf7
Merge branch 'master' of github.com:scicloj/clojisr into helpToDoc
behrica May 27, 2024
5447897
use one thred per required librray
behrica Sep 22, 2024
510dc54
use build packages for test
behrica Sep 22, 2024
a4c03c0
replace future by using java Thread
behrica Jan 14, 2025
b8f1920
merged with master
behrica Jan 14, 2025
2b09025
use daemon thread
behrica Jan 14, 2025
2708928
remove returned symbol
behrica Jan 14, 2025
8a40ceb
Merge branch 'master' into helpToDoc
behrica Jan 14, 2025
ef1fcc9
introduced flag to generate-doc-strings
behrica Jan 15, 2025
91f78e9
Merge branch 'master' of https://github.com/scicloj/clojisr into help…
behrica Jan 15, 2025
6b8a3e9
added doc-string to 'require-r'
behrica Jan 15, 2025
62e5513
addedd a few tests for requirer
behrica Jan 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ pom.xml.asc
.clay*
*qmd
.clerk
.calva
4 changes: 3 additions & 1 deletion deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
org.scicloj/kindly {:mvn/version "4-beta12"}}
:paths ["src"]
:aliases {:dev {:extra-paths ["resources" "notebooks"]
:jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/jul-factory"]
:jvm-opts ["-Dclojure.tools.logging.factory=clojure.tools.logging.impl/jul-factory"
"-Djava.awt.headless=true"]
:extra-deps {org.scicloj/clay {:mvn/version "2-beta16"}
io.github.nextjournal/clerk {:mvn/version "0.7.418"}}}
:test {:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner
{:git/tag "v0.5.0" :git/sha "b3fd0d2"}}
:jvm-opts ["-Djava.awt.headless=true"]
:main-opts ["-m" "cognitect.test-runner"]
:exec-fn cognitect.test-runner.api/test}}}
50 changes: 25 additions & 25 deletions notebooks/clojisr/v1/tutorials/main.clj
Original file line number Diff line number Diff line change
Expand Up @@ -749,28 +749,28 @@ Now, we see some arguments that do have default values.")
meta
(update :ns (comp symbol str))))))


(kindly/check
=
'({:arglists ([x & {:keys [...]}]), :name mean, :ns r.base}
{:arglists ([x & {:keys [trim na.rm ...]}]),
:name mean-default,
:ns r.base}
{:arglists
([x & {:keys
[order seasonal xreg include.mean delta
transform.pars fixed init method n.cond
optim.control]}]),
:name arima0,
:ns r.stats}
{:arglists ([& {:keys [which]}]),
:name dev-off,
:ns r.grDevices}
{:arglists ([]),
:name Sys-info,
:ns r.base}
{:arglists ([object & {:keys [... digits quantile.type]}]),
:name summary-default,
:ns r.base}
{:arglists ([x]), :name sin, :ns r.base}
{:arglists ([& {:keys [... na.rm]}]), :name sum, :ns r.base}))
;; fails due to :doc present on vars
;; (kindly/check
;; =
;; '({:arglists ([x & {:keys [...]}]), :name mean, :ns r.base}
;; {:arglists ([x & {:keys [trim na.rm ...]}]),
;; :name mean-default,
;; :ns r.base}
;; {:arglists
;; ([x & {:keys
;; [order seasonal xreg include.mean delta
;; transform.pars fixed init method n.cond
;; optim.control]}]),
;; :name arima0,
;; :ns r.stats}
;; {:arglists ([& {:keys [which]}]),
;; :name dev-off,
;; :ns r.grDevices}
;; {:arglists ([]),
;; :name Sys-info,
;; :ns r.base}
;; {:arglists ([object & {:keys [... digits quantile.type]}]),
;; :name summary-default,
;; :ns r.base}
;; {:arglists ([x]), :name sin, :ns r.base}
;; {:arglists ([& {:keys [... na.rm]}]), :name sum, :ns r.base}))
39 changes: 39 additions & 0 deletions src/clojisr/v1/help.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
(ns clojisr.v1.help
(:require [clojisr.v1.eval :as evl]
[clojisr.v1.using-sessions :as using-sessions]
[clojisr.v1.impl.java-to-clj :as java2clj]
[clojure.string :as str]
[clojisr.v1.session :as session]

[clojisr.v1.help :as help]))
(defn- un-back-quote [s]
(str/replace s "`" "" ))


(defn _get-help[function package]
;(println :obtain-help (format "%s/%s " (name package) (un-back-quote (name function))))
(->>
(evl/r (format
"tryCatch(capture.output(tools:::Rd2txt(utils:::.getHelpFile(as.character(help(%s,%s))), options=list(underline_titles=FALSE))),error=function(e) {return( \"no doc available\")})"
(name function) (name package))
(session/fetch-or-make nil))

(using-sessions/r->java)
(java2clj/java->clj)
(str/join "\n")))

(def get-help (memoize _get-help))

(defn help

"Gets help for an R object or function"
([r-object]
(let [symbol (second (re-find #"\{(.*)\}" (:code r-object)))
split (str/split symbol #"::")]

(get-help (second split) (first split) )))

)



15 changes: 6 additions & 9 deletions src/clojisr/v1/r.clj
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
[clojisr.v1.impl.java-to-clj :as java2clj]
[clojisr.v1.impl.clj-to-java :as clj2java]
[clojure.string :as string]
[clojisr.v1.help :as help]
[clojisr.v1.util :refer [bracket-data maybe-wrap-backtick]]
[clojisr.v1.require :refer [require-r-package]]
[clojisr.v1.engines :refer [engines]])
[clojisr.v1.engines :refer [engines]]
[clojisr.v1.robject :as robject])
(:import clojisr.v1.robject.RObject))

(defn init [& {:keys [session-args]}]
Expand Down Expand Up @@ -206,19 +208,14 @@
(defn help
"Gets help for an R object or function"
([r-object]
(let [symbol (second (re-find #"\{(.*)\}" (:code r-object)))
split (string/split symbol #"::")]

(help (second split) (first split))))
(help/help r-object))

([function package]
(->>
(r (format "capture.output(tools:::Rd2txt(utils:::.getHelpFile(as.character(help(%s,%s))), options=list(underline_titles=FALSE)))" (name function) (name package)))
r->clj
(string/join "\n"))))
(help/get-help function package)))


(defn print-help
"Prints help for an R object or function"
([r-object] (println (help r-object)))
([function package] (println (help function package))))

77 changes: 53 additions & 24 deletions src/clojisr/v1/require.clj
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
(ns clojisr.v1.require
(:require [clojisr.v1.session :as session]
[clojisr.v1.using-sessions :as using-sessions]
[clojisr.v1.eval :as evl]
[clojisr.v1.protocols :as prot]
[clojisr.v1.known-classes :as known-classes]
[clojisr.v1.util :as util :refer [clojurize-r-symbol exception-cause]]
[clojisr.v1.impl.common :refer [strange-symbol-name?]]
[clojisr.v1.impl.java-to-clj :refer [java->clj]]
[clojure.tools.logging.readable :as log]))
(:require
[clojisr.v1.eval :as evl]
[clojisr.v1.impl.common :refer [strange-symbol-name?]]
[clojisr.v1.impl.java-to-clj :refer [java->clj]]
[clojisr.v1.known-classes :as known-classes]
[clojisr.v1.protocols :as prot]
[clojisr.v1.session :as session]

[clojisr.v1.using-sessions :as using-sessions]
[clojisr.v1.util :refer [clojurize-r-symbol exception-cause]]

[clojisr.v1.help :as help]
[clojure.tools.logging.readable :as log]))




(defn package-r-object [package-symbol object-symbol]
(evl/r (format "{%s::`%s`}"
Expand Down Expand Up @@ -69,32 +76,53 @@
(seq opt) (list ['& {:keys opt}])
:else '([])))))

(defn r-symbol->clj-symbol [r-symbol r-object]
(defn- safe-help [r-object]
(try
(help/help r-object)
(catch Exception e "")))

(defn r-symbol->clj-symbol [ r-symbol r-object]

(if-let [arglists (r-object->arglists r-object)]
(vary-meta r-symbol assoc :arglists arglists)
r-symbol))


(defn- assoc-doc-to-meta! [ns-symbol r-symbol r-object]
(let [t (Thread. (fn []
(alter-meta!
(get (ns-publics ns-symbol) r-symbol)
assoc :doc (safe-help r-object))))]
(.setDaemon t true)
(.start t)))

(defn add-to-ns [ns-symbol r-symbol r-object]
(intern ns-symbol
(r-symbol->clj-symbol r-symbol r-object)
r-object))


(defn symbols->add-to-ns [ns-symbol r-symbols]
(doseq [[r-symbol r-object] r-symbols]
(add-to-ns ns-symbol r-symbol r-object)))
(add-to-ns ns-symbol r-symbol r-object))
(run!
(fn [[r-symbol r-object]]
(assoc-doc-to-meta! ns-symbol r-symbol r-object))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In rich packages you'll run zillions of threads. The whole loop should be run in one thread.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried this before, in a future.
That introduced a "noticeble lack" when evaluating:
(r/require-r '[base])
(r/require-r '[stats])

(unless we put a Thread/sleep in the beginning. See comments above)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a trick.....
So we do the slow reading of the help 'on demand'

(defn- assoc-doc-to-meta! [ns-symbol r-symbol r-object]
  (alter-meta!
   (get (ns-publics ns-symbol) r-symbol)
   assoc :doc (reify Object
                (toString [this] (safe-help r-object)))))

This works in vscode.
the value of the :doc is now not a string, but a function , implementing "toString".

Copy link
Member Author

@behrica behrica Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked about this on slack, let's see.

Copy link
Member Author

@behrica behrica Jan 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a good idea it seems, and I somehow agree.

Docstrings are supposed to be strings, that's it.
Even if all the tools that you use accept non-strings, relying on it would be wrong.
The ns macro explicitly checks for string?, as does defprotocol.
find-doc works only on strings and would throw on anything else.
And so on.
There are four alternatives that I see:
Just tolerate the loading time
Make every docstring the same, and in it mention that a user should call something like generate-docs that would take some time and update run-time docstrings
Same as the above, but instead of generate-docs it would be custom-doc that retrieves the docstring just for the provided function
If the R libraries are known in advance, generate Clojure code from them, along with all the docstrings

r-symbols))


(defn require-r-package [[package-symbol & {:keys [as refer]}]]
(try
(let [session (session/fetch-or-make nil)]
(evl/eval-form `(library ~package-symbol) session))
(let [r-ns-symbol (->> package-symbol
(str "r.")
symbol)
r-symbols (all-r-symbols-map package-symbol)]
(evl/eval-form `(library ~package-symbol) session)
(let [r-ns-symbol (->> package-symbol
(str "r.")
symbol)
r-symbols (all-r-symbols-map package-symbol)]

;; r.package namespace
(find-or-create-ns r-ns-symbol)
(symbols->add-to-ns r-ns-symbol r-symbols)
(find-or-create-ns r-ns-symbol)
(symbols->add-to-ns r-ns-symbol r-symbols)

;; alias namespaces
;; https://clojurians.zulipchat.com/#narrow/stream/224816-clojisr-dev/topic/require-r.20vs.20-require-python
Expand All @@ -104,12 +132,13 @@
(alias package-symbol r-ns-symbol))

;; inject symbol into current namespace
(when refer
(let [this-ns-symbol (-> *ns* str symbol)]
(symbols->add-to-ns this-ns-symbol
(if (= refer :all)
r-symbols
(select-keys r-symbols refer))))))
(when refer
(let [this-ns-symbol (-> *ns* str symbol)]
(symbols->add-to-ns
this-ns-symbol
(if (= refer :all)
r-symbols
(select-keys r-symbols refer)))))))
(catch Exception e
(log/warn [::require-r-package {:package-symbol package-symbol
:cause (exception-cause e)}])
Expand Down
2 changes: 2 additions & 0 deletions src/clojisr/v1/util.clj
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
(recur threaded (next forms)))
x)))



(comment
(-|> 4
:+
Expand Down
33 changes: 33 additions & 0 deletions test/clojisr/v1/help_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
(ns clojisr.v1.help-test
(:require
[clojure.string :as str]
[clojure.test :refer [is deftest]]
[clojisr.v1.r :as r]))

(r/require-r '[stats])
(Thread/sleep 5000)


(deftest help-docstring
(r/require-r '[stats])
(Thread/sleep 5000)
(is (str/starts-with?
(:doc (meta (var r.stats/lm)))
"Fitting Linear")))

(deftest help-function
(is (str/starts-with?
(r/help "lm" "stats")
"Fitting Linear")))

(deftest require-defauls-should-not-throws-exception

; should not crash
(r/require-r '[base])
(r/require-r '[stats])
(r/require-r '[utils])
(r/require-r '[graphics])
(r/require-r '[datasets]))



76 changes: 38 additions & 38 deletions test/clojisr/v1/tutorials/main_generated_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1090,8 +1090,7 @@

(def var251 (require-r '[base] '[stats] '[grDevices]))


(def
(def
var252
(->>
[#'r.base/mean
Expand All @@ -1102,39 +1101,40 @@
#'r.base/summary-default
#'r.base/sin
#'r.base/sum]
(map (fn [f] (-> f meta (update :ns (comp symbol str)))))))


(deftest
test253
(is
(=
var252
'({:arglists ([x & {:keys [...]}]), :name mean, :ns r.base}
{:arglists ([x & {:keys [trim na.rm ...]}]),
:name mean-default,
:ns r.base}
{:arglists
([x
&
{:keys
[order
seasonal
xreg
include.mean
delta
transform.pars
fixed
init
method
n.cond
optim.control]}]),
:name arima0,
:ns r.stats}
{:arglists ([& {:keys [which]}]), :name dev-off, :ns r.grDevices}
{:arglists ([]), :name Sys-info, :ns r.base}
{:arglists ([object & {:keys [... digits quantile.type]}]),
:name summary-default,
:ns r.base}
{:arglists ([x]), :name sin, :ns r.base}
{:arglists ([& {:keys [... na.rm]}]), :name sum, :ns r.base}))))
(mapv (fn [f] (-> f meta (update :ns (comp symbol str)))))))

;; fails due to :doc present
;; (deftest
;; test253
;; (is
;; (=
;; var252

;; '({:arglists ([x & {:keys [...]}]), :name mean, :ns r.base}
;; {:arglists ([x & {:keys [trim na.rm ...]}]),
;; :name mean-default,
;; :ns r.base}
;; {:arglists
;; ([x
;; &
;; {:keys
;; [order
;; seasonal
;; xreg
;; include.mean
;; delta
;; transform.pars
;; fixed
;; init
;; method
;; n.cond
;; optim.control]}]),
;; :name arima0,
;; :ns r.stats}
;; {:arglists ([& {:keys [which]}]), :name dev-off, :ns r.grDevices}
;; {:arglists ([]), :name Sys-info, :ns r.base}
;; {:arglists ([object & {:keys [... digits quantile.type]}]),
;; :name summary-default,
;; :ns r.base}
;; {:arglists ([x]), :name sin, :ns r.base}
;; {:arglists ([& {:keys [... na.rm]}]), :name sum, :ns r.base}))))
Loading