Skip to content

Instantly share code, notes, and snippets.

@cursive-ide
Created October 24, 2024 14:11
Show Gist options
  • Save cursive-ide/4b7af95e6d6699d1b0a0962b9318290f to your computer and use it in GitHub Desktop.
Save cursive-ide/4b7af95e6d6699d1b0a0962b9318290f to your computer and use it in GitHub Desktop.
File translation prompt from my Conj 2024 talk
Follow these steps when translating code to Kotlin:
1. Analyze the source code to understand its structure and functionality.
2. Identify language-specific constructs or idioms in the source language that may need special attention when translating to Kotlin.
3. Translate the code to Kotlin, ensuring that you:
a. Use Kotlin's syntax and language features appropriately.
b. Maintain the original code's functionality and logic.
c. Apply Kotlin best practices and idioms where applicable.
d. Preserve the overall structure and organization of the code when possible.
4. Add comments to explain any significant changes or decisions made during the translation process.
Provide your Kotlin translation within an artifact. After the translated code, include
a brief explanation of any major changes or decisions you made during the translation process
within <translation_notes> tags.
If you encounter any parts of the code that you're unsure how to translate or that require
additional context, note these issues within <uncertainties> tags after your translation notes.
Remember to:
- Use Kotlin's concise syntax where appropriate (e.g., type inference, expression bodies).
- Utilize Kotlin's null safety features.
- Convert mutable variables to immutable ones (using 'val') where possible.
- Use Kotlin's range expressions and for loops when translating loop constructs.
- Implement Kotlin's extension functions if they can improve code readability or organization.
- Adapt object-oriented constructs to Kotlin's conventions (e.g., data classes, companion objects).
Focus on recreating the functionality in a Kotlin-idiomatic way rather than doing a line-by-line translation.
This code uses transducers. A kotlin port of the transducer API is available, please translate
the Clojure code to use the Kotlin transducer equivalents. Here is a summary of the available API:
<api_summary>
class StepFunction
function apply(R, T, AtomicBoolean): R
function makeStepFunction((R, T) -> R): StepFunction<R, T>
object object : StepFunction<R, T>
function apply(R, T, AtomicBoolean): R
class ReducingFunction
function apply(): R
function apply(R): R
class ReducingFunctionOn
constructor ReducingFunctionOn(ReducingFunction<R, A>)
parameter rf: ReducingFunction<R, A>
function apply(): R
function apply(R): R
class Transducer
function apply(ReducingFunction<R, B>): ReducingFunction<R, C>
function comp(Transducer<A, in B>): Transducer<A, C>
object object : Transducer<A, C>
function apply(ReducingFunction<R, A>): ReducingFunction<R, C>
function reduce(StepFunction<R, T>, R, Iterable<T>, AtomicBoolean): R
class Reducible
function reduce(StepFunction<R, T>, R, AtomicBoolean): R
function completing(StepFunction<R, T>): ReducingFunction<R, T>
object object : ReducingFunction<R, T>
function apply(R, T, AtomicBoolean): R
function transduce(Transducer<A, B>, ReducingFunction<R, A>, Iterable<B>): R
function transduce(Transducer<A, B>, ReducingFunction<R, A>, Reducible<B>): R
function transduce(Transducer<A, B>, StepFunction<R, A>, Iterable<B>): R
function transduce(Transducer<A, B>, StepFunction<R, A>, Reducible<B>): R
function transduce(Transducer<A, B>, (R, A) -> R, Iterable<B>): R
function transduce(Transducer<A, B>, (R, A) -> R, Reducible<B>): R
function transduce(Transducer<A, B>, StepFunction<R, A>, R, Iterable<B>): R
function transduce(Transducer<A, B>, StepFunction<R, A>, R, Reducible<B>): R
function transduce(Transducer<A, B>, (R, A) -> R, R, Iterable<B>): R
function transduce(Transducer<A, B>, (R, A) -> R, R, Reducible<B>): R
function run((A) -> Unit, Iterable<A>): Unit
object object : StepFunction<Any?, A>
function apply(Any?, A, AtomicBoolean): Any?
function run((A) -> Unit, Reducible<A>): Unit
object object : StepFunction<Any?, A>
function apply(Any?, A, AtomicBoolean): Any?
function eduction(Transducer<A, B>, Iterable<B>): Reducible<A>
object object : Reducible<A>
function reduce(StepFunction<R, A>, R, AtomicBoolean): R
object object : ReducingFunction<R, A>
function apply(R, A, AtomicBoolean): R
function eduction(Transducer<A, B>, Reducible<B>): Reducible<A>
object object : Reducible<A>
function reduce(StepFunction<R, A>, R, AtomicBoolean): R
function compose(Transducer<B, C>, Transducer<A, B>): Transducer<A, C>
function map((B) -> A): Transducer<A, B>
object object : Transducer<A, B>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, B>
object object : ReducingFunctionOn<R, A, B>(rf)
function apply(R, B, AtomicBoolean): R
function filter((A) -> Boolean): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
function apply(R, A, AtomicBoolean): R
function cat(): Transducer<A, Iterable<A>>
object object : Transducer<A, Iterable<A>>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, Iterable<A>>
object object : ReducingFunctionOn<R, A, Iterable<A>>(rf)
function apply(R, Iterable<A>, AtomicBoolean): R
function mapcat((C) -> B): Transducer<A, C>
function remove((A) -> Boolean): Transducer<A, A>
function take(Int): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
property taken: Int
function apply(R, A, AtomicBoolean): R
function takeWhile((A) -> Boolean): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
function apply(R, A, AtomicBoolean): R
function distinct(): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunction<R, A>
object object : ReducingFunction<R, A>
property intercepted: MutableSet<A>
function apply(R): R
function apply(R, A, AtomicBoolean): R
function apply(): R
function drop(Int): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
property dropped: Int
function apply(R, A, AtomicBoolean): R
function dropWhile((A) -> Boolean): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
property drop: Boolean
function apply(R, A, AtomicBoolean): R
function takeNth(Int): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
property nth: Int
function apply(R, A, AtomicBoolean): R
function replace(Map<A, A>): Transducer<A, A>
function keep((A) -> A?): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
function apply(R, A, AtomicBoolean): R
function keepIndexed((Int, A) -> A?): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
property n: Int
function apply(R, A, AtomicBoolean): R
function dedupe(): Transducer<A, A>
object object : Transducer<A, A>
function apply(ReducingFunction<R, A>): ReducingFunctionOn<R, A, A>
object object : ReducingFunctionOn<R, A, A>(rf)
property prior: A?
function apply(R, A, AtomicBoolean): R
function randomSample(Double): Transducer<A, A>
function partitionBy((A) -> P): Transducer<Iterable<A>, A>
object object : Transducer<Iterable<A>, A>
function apply(ReducingFunction<R, Iterable<A>>): ReducingFunction<R, A>
object object : ReducingFunction<R, A>
property part: ArrayList<A>
property mark: Any
property prior: Any?
function apply(): R
function apply(R): R
function apply(R, A, AtomicBoolean): R
function partitionAll(Int): Transducer<Iterable<A>, A>
object object : Transducer<Iterable<A>, A>
function apply(ReducingFunction<R, Iterable<A>>): ReducingFunction<R, A>
object object : ReducingFunction<R, A>
property part: ArrayList<A>
function apply(): R
function apply(R): R
function apply(R, A, AtomicBoolean): R
function Transducer<B, C>.plus(Transducer<A, in B>): Transducer<A, C>
function intoList(Xf<A, B>, Iterable<B>): List<A>
function intoSet(Xf<A, B>, Iterable<B>): Set<A>
function intoList(Xf<A, B>, Reducible<B>): List<A>
function intoSet(Xf<A, B>, Reducible<B>): Set<A>
function intoList(Reducible<A>): List<A>
function intoSet(Reducible<A>): Set<A>
function intoMap(Xf<Pair<A, B>, C>, Iterable<C>): Map<A, B>
function mapOfLists(Xf<Pair<A, B>, C>, Iterable<C>): Map<A, List<B>>
function mapOfSets(Xf<Pair<A, B>, C>, Iterable<C>): Map<A, Set<B>>
function intoMap(Xf<Pair<A, B>, C>, () -> T, Iterable<C>): Map<A, T>
function intoRf(): Rf<R, A>
object object : Rf<R, A>
function apply(R, A, AtomicBoolean): R
function intoMapRf(): Rf<R, Pair<A, B>>
object object : Rf<R, Pair<A, B>>
function apply(R, Pair<A, B>, AtomicBoolean): R
function intoMapGroupRf(() -> C): Rf<R, Pair<A, B>>
object object : Rf<R, Pair<A, B>>
function apply(R, Pair<A, B>, AtomicBoolean): R
</api_summary>
Here are some examples of the equivalent functions or classes, first the Clojure original and
then the Kotlin equivalent you should translate it to:
<translation_examples>
<from>clojure.lang.IReduceInit</from>
<to>cursive.transducers.Reducible</to>
<from>clojure.core/into []</from>
<to>cursive.transducers.intoList</to>
<from>clojure.core/into #{}</from>
<to>cursive.transducers.intoSet</to>
<from>clojure.core/transduce</from>
<to>cursive.transducers.transduce</to>
<from>clojure.core/eduction</from>
<to>cursive.transducers.eduction</to>
</translation_examples>
Transducer xforms can just be composed in Kotlin with infix +.
This code uses the cursive.names namespace. A Kotlin port of this namespace is available, translate
the Clojure code to use the Kotlin equivalents. Uses of names/name and names/qualified-name should be replaced with the className and classFqn extension properties when invoked on PsiClass objects. The name and qualifiedName extension properties should be used for other types. Here is a summary of the ported API:
<api_summary>
property PsiMethod.qualifiedName: String
property PsiField.qualifiedName: String
property ClSymbol.qualifiedName: String
property PsiClass.className: String
property PsiClass.classFqn: String
property ClSymbol.looksLikeMethodOrField: Boolean
property ClSymbol.looksLikeConstructor: Boolean
</api_summary>
This code uses the cursive.application namespace. A Kotlin port of this namespace is available, translate
the Clojure code to use the Kotlin equivalents. Here is a summary of the ported API:
<api_summary>
function withReadAction(() -> T): T
function withWriteAction(() -> T): T
function withWriteCommand(Project, () -> T): T
function withDocumentCommand(Project, String, Document, () -> Unit): Unit
function withInvisibleCommand(Project, () -> Unit): Unit
function invokeLater(() -> Unit): Unit
function invokeLater(ModalityState, () -> Unit): Unit
function invokeAndWait(() -> T): T
function onPooled(() -> T): Future<T>
function isUnitTest(): Boolean
</api_summary>
Here are the signatures of the interop methods referenced by this code:
cursive.leiningen.project.LeinShimProvider.Companion cursive.leiningen.project.LeinShimProvider getInstance()
cursive.config.ClojureModuleExtension com.intellij.execution.ShortenCommandLine getShortenStubCommandLine()
cursive.extensionPoints.ModuleDirectoryFinder.Companion com.intellij.openapi.vfs.VirtualFile find(com.intellij.openapi.module.Module module)
cursive.leiningen.LeiningenProjectSettings java.util.List<java.lang.String> getSelectedProfiles()
cursive.repl.REPLUtil void configureClasspath(com.intellij.openapi.module.Module module, com.intellij.execution.configurations.JavaParameters params, boolean addSources)
cursive.runner.UtilsKt void configureJdkFromModule(com.intellij.execution.configurations.JavaParameters params, com.intellij.openapi.module.Module module)
cursive.leiningen.LeiningenUtil java.util.Map<java.lang.String,java.lang.Object> standardArgs()
cursive.leiningen.LeiningenProjectSettings.Companion java.lang.String currentVersion(com.intellij.openapi.project.Project project)
cursive.config.ClojureModuleExtension void setWarnOnStderr(boolean warn)
cursive.config.ClojureModuleExtension boolean getCreateStubs()
cursive.leiningen.LeiningenSupportKt cursive.build.BuildSystemId getSYSTEM_ID()
cursive.build.BuildSystemPropertyManager java.lang.String getBuildSystemId()
cursive.leiningen.LeiningenProjectSettings.Companion cursive.leiningen.LeiningenProjectSettings getInstance(com.intellij.openapi.project.Project project)
cursive.config.ClojureModuleExtension void setCreateStubs(boolean createStubs)
cursive.config.ClojureModuleExtension.Companion cursive.config.ClojureModuleExtension getInstance(com.intellij.openapi.module.Module module)
cursive.leiningen.project.LeinShimProvider.Companion java.io.File leiningenFile(java.lang.String version)
cursive.config.ClojureModuleExtension boolean getWarnOnStderr()
cursive.build.BuildSystemId java.lang.String getId()
cursive.shim.ShimProvider T withShim(com.intellij.openapi.project.Project project, java.lang.String version, kotlin.jvm.functions.Function1<? super java.util.function.Function<java.lang.Object,java.lang.Object>,? extends T> block)
cursive.build.BuildSystemPropertyManager cursive.build.BuildSystemPropertyManager getInstance(com.intellij.openapi.module.Module module)
cursive.settings.ClojureIdeSettings cursive.settings.ClojureIdeSettings getInstance()
cursive.stubs.ReplStubsNotifications.Companion com.intellij.notification.NotificationGroup getNOTIFICATION_GROUP()
cursive.shim.IndicatorBridge IndicatorBridge(com.intellij.openapi.project.Project project, com.intellij.openapi.progress.ProgressIndicator indicator, java.lang.String notificationGroupName)
Here is the source code of the project vars referenced by this code, organised by namespace:
<clojure_code>
(ns cursive.stubs.metadata.clojurescript)
(defn read-namespace
"Read a ClojureScript namespace and return its public vars.
The keys in the returned map are:
:name - the name of the namespace
:doc - the doc-string on the namespace
:author - if the metadata is there, we return it
:publics
:name - the name of a public function, macro, or value
:arglists - the arguments the function or macro takes
:doc - the doc-string of the var
:type - one of :macro, :protocol, :multimethod or :var
:added - the library version the var was added in
:deprecated - the library version the var was deprecated in"
[resource]
(let [{:keys [ns requires]} (ana/parse-ns resource)
js-dependencies (into #{} (filter string?) requires)]
(read-file ns resource js-dependencies)))
</clojure_code>
<clojure_code>
(ns cursive.intellij.notify)
(defn warn
"Creates and shows an informational notification, using the passed
title and text. group-id can be either a string or a
NotificationGroup."
([project group-id title text]
(notify project group-id title text NotificationType/WARNING nil))
([project group-id title text actions]
(notify project group-id title text NotificationType/WARNING actions)))
(defn error
"Creates and shows an informational notification, using the passed
title and text. group-id can be either a string or a
NotificationGroup."
([project group-id title text]
(notify project group-id title text NotificationType/ERROR nil))
([project group-id title text actions]
(notify project group-id title text NotificationType/ERROR actions)))
(defn action [text doit]
(NotificationAction/createSimpleExpiring text doit))
</clojure_code>
<clojure_code>
(ns cursive.index)
(defn short-names-by-namespace
"#{short-name...}"
[project ns scope language]
(string-set-index project CljIndex/SYMBOL_META (language/with-language (str "ns:" ns) language) scope))
(defn dependencies
"lang:ns -> {lang:ns -> creates-cycles?}
The scope here is complicated. An additional lookup will be performed for each
cljs dependency found, to determine whether that dependency requires its own
macros. If so, the macro namespace will also be added as a dependency to the
returned set. However, if the second lookup is not covered by the scope provided,
the macro namespace will not be returned. This is probably a TODO, but for now,
caveat caller."
([ns scope language]
(dependencies (language/with-language ns language) scope))
([ns scope]
(let [index (FileBasedIndex/getInstance)
deps (apply merge (.getValues index CljIndex/DEPENDENCIES ns scope))
cljs-deps (into [] (filter #(str/starts-with? % "cljs:")) (keys deps))]
(reduce (fn [deps cljs-dep]
(let [transitive-deps (apply merge (.getValues index CljIndex/DEPENDENCIES cljs-dep scope))
clj-dep (str "clj:" (subs cljs-dep 5))]
(if (contains? transitive-deps clj-dep)
(assoc deps clj-dep (get transitive-deps clj-dep))
deps)))
deps
cljs-deps))))
(defn namespace-names
"Returns a set of namespace names. If called with just a scope, returns all names
with a language prefix. If called with an explicit language, returns only namespaces
for that language with no prefix."
([scope]
(let [index (FileBasedIndex/getInstance)]
(apply set/union (.getValues index CljIndex/NAMESPACES "ns:names" scope))))
([scope language]
(->> (namespace-names scope)
(filter #(= language (language/from-key %)))
(map language/strip-language)
(set))))
</clojure_code>
<clojure_code>
(ns cursive.dumb)
(defn wait-till-smart [project]
(let [result (promise)]
(when-smart project
(deliver result :smart))
@result))
(defmacro when-smart
[project & body]
`(.runWhenSmart
(DumbService/getInstance ~project)
(fn [] ~@body)))
</clojure_code>
<clojure_code>
(ns cursive.leiningen.exec)
(defn lein-parameters
"Takes a command line string as generated by leiningen and converts
it to a JavaParameters. Does not initialise the JDK."
[project command in-leiningen?]
(let [agent-arg? #(or (.startsWith ^String % "-javaagent")
(.startsWith ^String % "-Xbootclasspath"))
jvm-args (take-while agent-arg? command)
command (drop-while agent-arg? command)
classpath? (= "-classpath" (first command))
classpath (if classpath? (str/split (second command) (Pattern/compile (str File/pathSeparatorChar))))
command (if classpath? (drop 2 command) command)
jvm-args (concat jvm-args (take-while #(not (= "clojure.main" %)) command))
args (rest (drop-while #(not (= "clojure.main" %)) command))
params (JavaParameters.)
vm-params ^ParametersList (.getVMParametersList params)
program-params ^ParametersList (.getProgramParametersList params)]
(doseq [arg jvm-args]
(.add vm-params ^String arg))
(doseq [item classpath]
(.add (.getClassPath params) ^String item))
(when in-leiningen?
(let [version (-> LeiningenProjectSettings/Companion
(.currentVersion project))
file (.leiningenFile LeinShimProvider/Companion version)]
(.add (.getClassPath params) (.getCanonicalPath file))))
(.setMainClass params ClojureUtils/CLOJURE_MAIN)
(.addAll program-params ^List (vec args))
params))
</clojure_code>
<clojure_code>
(ns cursive.project)
(defprotocol Coerce
(from ^com.intellij.openapi.project.Project [element]))
</clojure_code>
<clojure_code>
(ns cursive.task)
(defn queue [^Task task]
(.queue task))
(defn backgroundable
"Creates a backgroundable task. task should be a function accepting a
ProgressIndicator."
([project title task]
(backgroundable project title true task))
([project title cancellable? task]
(extend-class Task$Backgroundable [project title cancellable?]
(run [this indicator] (task indicator)))))
</clojure_code>
<clojure_code>
(ns cursive.logging)
(defmacro debug [& args]
`(let [logger# (get-logger ~(str \# *ns*))]
(when (.isDebugEnabled logger#)
(do-log logger# do-debug ~@args))))
</clojure_code>
<clojure_code>
(ns cursive.progress)
(defn canceled? [^ProgressIndicator indicator]
(.isCanceled indicator))
(defn indeterminate [^ProgressIndicator indicator indeterminate?]
(.setIndeterminate indicator indeterminate?))
(defn indicator []
(.getProgressIndicator (ProgressManager/getInstance)))
(defn fraction [^ProgressIndicator indicator num total]
(.setFraction indicator (double (/ num total))))
(defn text [^ProgressIndicator indicator message]
(.setText indicator message))
(defn subtext [^ProgressIndicator indicator message]
(.setText2 indicator message))
</clojure_code>
<clojure_code>
(ns cursive.language)
(defn strip-language [key]
(let [index (.indexOf ^String key (int \:))]
(if (pos? index) (subs key (inc index)) key)))
(defn from-key [key]
(let [index (.indexOf ^String key (int \:))]
(if (pos? index) (keyword (subs key 0 index)))))
</clojure_code>
<clojure_code>
(ns cursive.stubs.metadata.clojure)
(defn read-ns [namespace]
(require namespace)
(-> (find-ns namespace)
(meta)
(select-keys [:doc :author :deprecated :added])
(assoc :name namespace)
(assoc :publics (read-publics namespace))))
</clojure_code>
<clojure_code>
(ns cursive.stubs.output)
(defn stub-string [{:keys [name publics]} existing-names]
(str
"(ns " name ")\n\n"
(str/join
"\n\n"
(for [{:keys [name arglists doc type members] :as info}
(sort-by :name publics)
:when (not (contains? existing-names (clojure.core/name name)))]
(let [remaining (dissoc info :name :arglists :doc :type)]
(case type
:protocol (str "(defprotocol " name "\n "
(when doc (str (doc-string doc) "\n "))
(str/join "\n " (map protocol-method members))
")")
:multimethod (str "(defmulti "
(when-not (empty? remaining) (str "^" (pr-str remaining) " "))
name
(when doc (str " " (doc-string doc)))
" :unknown)")
:macro (str "(defmacro "
(when-not (empty? remaining) (str "^" (pr-str remaining) " "))
name
(when doc (str " " (doc-string doc)))
(formatted-arglists arglists)
")")
; else should be var
(if (seq arglists)
(str "(defn "
(when-not (empty? remaining) (str "^" (pr-str remaining) " "))
name
(when doc (str " " (doc-string doc)))
(formatted-arglists arglists)
")")
(str "(def "
(when-not (empty? remaining) (str "^" (pr-str remaining) " "))
name
(when doc (str " " (doc-string doc)))
")"))))))))
</clojure_code>
<clojure_code>
(ns cursive.leiningen.module)
(defn lein-module? [^Module module]
(= (-> (BuildSystemPropertyManager/getInstance module)
(.getBuildSystemId))
(.getId (LeiningenSupportKt/getSYSTEM_ID))))
(defn find-project-path [module]
(let [roots-manager (ModuleRootManager/getInstance module)]
(some #(when-let [file (.findChild ^VirtualFile % "project.clj")]
(.getPath file))
(.getContentRoots roots-manager))))
</clojure_code>
<clojure_code>
(ns cursive.names)
(defprotocol Named
(name ^String [this]))
</clojure_code>
<clojure_code>
(ns cursive.application)
(defmacro invoke-later
"Runs body asynchronously on the EDT."
[& body]
`(.invokeLater
(ApplicationManager/getApplication)
(fn []
~@body)))
(defmacro with-write-action
"Runs body inside a write action."
[& body]
`(.runWriteAction
(ApplicationManager/getApplication)
(reify Computable
(compute [this] ~@body))))
(defmacro with-read-action
"Runs body inside a read action."
[& body]
`(.runReadAction
(ApplicationManager/getApplication)
(reify Computable
(compute [this] ~@body))))
</clojure_code>
<clojure_code>
(ns cursive.stubs)
(defn warn-on-err? [^Module module]
(let [extension (.getInstance ClojureModuleExtension/Companion module)]
(.getWarnOnStderr extension)))
(defn ^SimpleJavaParameters lein-parameters [^Module module ^File init-file]
(let [shim-provider (.getInstance LeinShimProvider/Companion)
project (project/from module)
settings (-> LeiningenProjectSettings/Companion
(.getInstance project))
version (-> LeiningenProjectSettings/Companion
(.currentVersion project))
indicator (progress/indicator)]
(if-let [result (.withShim shim-provider
project
version
(reify Function1
(invoke [_ shim]
(.apply ^Function shim
{"namespace" "cursive.leiningen.eval"
"var-name" "eval-details"
"arg" (merge (into {} (.standardArgs LeiningenUtil/INSTANCE))
{"project-file" (module/find-project-path module)
"script-file" (.getCanonicalPath ^File init-file)
"profiles" (str/join "," (.getSelectedProfiles settings))
"indicator" (IndicatorBridge. project indicator "Leiningen")})}))))]
(if-let [command (get result "command")]
(let [params (doto ^JavaParameters (exec/lein-parameters project command false)
(.configureByModule module JavaParameters/JDK_ONLY)
(.setWorkingDirectory (if-let [virtual-file (.find ModuleDirectoryFinder/Companion module)]
(File. (.getCanonicalPath virtual-file))
(.getParentFile init-file))))]
(add-runtime-jar params)
(.replaceOrAppend (.getVMParametersList params) "-agentlib:jdwp" "")
params)
(let [{:strs [exit-code message output error]} result
title (str "Leiningen aborted for module " (names/name module) (if exit-code (str ": exit code " exit-code)))
text (str (when message (str message "<br/>"))
"<pre>" (escape-html (str output)) "</pre>"
"<pre>" (escape-html (str error)) "</pre>")
group (-> ReplStubsNotifications/Companion
(.getNOTIFICATION_GROUP))]
(notify/error project group title text
[(notify/action (str "Disable stub generation for module " (names/name module))
(disable module))])
nil)))))
(defn ^String stub-file-name
"Generate a file name for the stubs for a namespace using a hash of the namespace
name, and the path to the virtual file containing it. The idea is to create a name
that will change if a library is updated. If no namespaces can be found, try to find
the namespace initialisation class instead, to handle binary-only libs (principally
Datomic). Returns nil if neither namespace nor class can be found."
([module namespace]
(stub-file-name module (language/strip-language namespace) (language/from-key namespace)))
([module namespace language]
(let [roots (into []
(comp
(filter #(instance? LibraryOrderEntry %))
(mapcat (fn [^LibraryOrderEntry entry]
(.getRootFiles entry OrderRootType/CLASSES)))
(filter #(FileUtilRt/extensionEquals (.getName ^VirtualFile %) "jar")))
(-> (ModuleRootManager/getInstance module)
(.getOrderEntries)))
base (-> namespace
(str/replace \- \_)
(str/replace \. \/))
lang-path (str base \. (name language))
cljc-path (str base ".cljc")
init-path (str base "__init.class")
source-files (eduction
cat
[(find-files roots lang-path)
(find-files roots cljc-path)])
files (if-not (empty? source-files)
source-files
(find-files roots init-path))]
; Only generate stubs for namespaces that we can find, see: https://github.com/cursive-ide/cursive/issues/2891
(when-not (empty? files)
(let [hash (reduce (fn [result vfile]
(unchecked-add-int
(unchecked-multiply-int (int 37) (int result))
(hash (.getCanonicalPath ^VirtualFile vfile))))
(hash namespace)
files)]
(str (Integer/toHexString hash) \. (name language)))))))
(defn find-files [roots path]
(eduction
(keep (fn [^VirtualFile file]
(.findFileByRelativePath file path)))
roots))
(defn enabled? [^Module module]
(let [extension (.getInstance ClojureModuleExtension/Companion module)]
(.getCreateStubs extension)))
(defn ^String module-stub-path [^Project project ^Module module]
(str (PathManager/getSystemPath) \/
"cursive_stubs_" (.getLocationHash project) \/
(Integer/toHexString
(-> (or (some-> (ProjectUtil/guessModuleDir module)
(.getPath))
(names/name module))
(.hashCode)))))
(defn ^SimpleJavaParameters java-parameters [module ^File init-file]
(let [params (doto (JavaParameters.)
(.setMainClass "clojure.main")
(.setWorkingDirectory (if-let [virtual-file (.find ModuleDirectoryFinder/Companion module)]
(File. (.getCanonicalPath virtual-file))
(.getParentFile init-file)))
(.setDefaultCharset (project/from module)))
args (.getProgramParametersList params)]
(UtilsKt/configureJdkFromModule params module)
(REPLUtil/configureClasspath module params false)
(add-runtime-jar params)
(.add args (.getAbsolutePath init-file))
params))
(defn create-script [project module clj-namespaces cljs-namespaces]
; This process is very prone to getting IndexNotReadyExceptions, try
; our best to retry if we get them, up to a point.
(let [ret (atom nil)]
(loop [count 0]
(if (>= count 5)
(throw (RuntimeException. "Unable to create stubs in 5 attempts"))
(let [done (promise)]
(dumb/when-smart project
(try
(reset! ret (str (when (seq clj-namespaces)
"(require 'cursive.stubs.metadata.clojure 'cursive.stubs.output)\n")
(str/join "\n" (->> clj-namespaces
(keep #(clj-namespace-form project module %))
(map pr-str)))
\newline
(when (seq cljs-namespaces)
"(require 'cursive.stubs.metadata.clojurescript 'cursive.stubs.output 'clojure.string 'clojure.java.io)\n")
(str/join "\n" (->> cljs-namespaces
(keep #(cljs-namespace-form project module %))
(map pr-str)))
\newline
"(System/exit 0)"))
(deliver done :done)
(catch IndexNotReadyException _
(deliver done :retry))
(catch Throwable ex
(deliver done ex))))
(let [result @done]
(if (instance? Throwable result)
(throw result)
(if-let [val @ret]
val
(do
(dumb/wait-till-smart project)
(recur (inc count)))))))))))
(defn ^String module-tmp-path [project module]
(str (module-stub-path project module) "-tmp"))
(defn disable [^Module module]
(let [root-manager (ModuleRootManager/getInstance module)
modifiable (.getModifiableModel root-manager)
extension (.getModuleExtension modifiable ClojureModuleExtension)]
(.setCreateStubs ^ClojureModuleExtension extension false)
(app/with-write-action
(.commit modifiable))))
(defn disable-warn-on-err [^Module module]
(let [root-manager (ModuleRootManager/getInstance module)
modifiable (.getModifiableModel root-manager)
extension (.getModuleExtension modifiable ClojureModuleExtension)]
(.setWarnOnStderr ^ClojureModuleExtension extension false)
(app/with-write-action
(.commit modifiable))))
(defn clj-namespace-form [project module namespace]
(when-let [file-name (stub-file-name module namespace :clj)]
(let [stubs-path (module-tmp-path project module)
stub-file-path (-> (File. stubs-path file-name)
(.getCanonicalPath))
existing-names (app/with-read-action
(set/union
(index/short-names-by-namespace project
namespace
(scope/module-with-deps-libs module)
:clj)
(index/short-names-by-namespace project
namespace
(scope/module-with-deps-libs module)
:cljc)))]
`(try
(println ~(str "Generating " namespace))
(spit ~stub-file-path
(let [ns-info# (cursive.stubs.metadata.clojure/read-ns (symbol ~namespace))]
(cursive.stubs.output/stub-string ns-info# ~existing-names)))
(catch Throwable t#
(.printStackTrace t#)
(System/exit 1))))))
(defn add-runtime-jar [^SimpleJavaParameters params]
(let [runtime-jar-path (PathManager/getResourceRoot ^Class ReplStubsSettings "/cursive/stubs/output.clj")]
(-> params
(.getClassPath)
(.add (io/file runtime-jar-path)))))
(defn ^String escape-html
"Change special characters into HTML character entities."
[text]
(.. ^String text
(replace "&" "&amp;")
(replace "<" "&lt;")
(replace ">" "&gt;")
(replace "\"" "&quot;")))
(defn finish-generation [project module]
(let [tmp-dir (File. (module-tmp-path project module))
final-dir (File. (module-stub-path project module))]
(FileUtil/delete final-dir)
(FileUtil/rename tmp-dir final-dir)
(app/invoke-later
(app/with-write-action
(let [fs (LocalFileSystem/getInstance)]
(when-let [dir (.refreshAndFindFileByIoFile fs final-dir)]
(->> dir
(.getChildren)
(into [])
(.refreshFiles fs))))))))
(defn cljs-namespace-form [project module namespace]
(when-let [file-name (stub-file-name module namespace :cljs)]
(let [stubs-path (module-tmp-path project module)
stub-file-path (-> (File. stubs-path file-name)
(.getCanonicalPath))
existing-names (app/with-read-action
(set/union
(index/short-names-by-namespace project
namespace
(scope/module-with-deps-libs module)
:cljs)
(index/short-names-by-namespace project
namespace
(scope/module-with-deps-libs module)
:cljc)))]
`(try
(println ~(str "Generating " namespace))
(spit ~stub-file-path
(let [file# (str/replace (munge ~namespace) \. \/)
resource# (or (io/resource (str file# ".cljs"))
(io/resource (str file# ".cljc")))
ns-info# (cursive.stubs.metadata.clojurescript/read-namespace resource#)]
(cursive.stubs.output/stub-string ns-info# ~existing-names)))
(catch Throwable t#
(.printStackTrace t#)
(System/exit 1))))))
(defn all-stubs-files
"namespaces is {language -> #{namespace...}}"
[project module stubs-dir namespaces]
(reduce-kv (fn [ret lang namespaces]
(reduce (fn [ret namespace]
(if-let [file-name (stub-file-name module namespace lang)]
(let [file (io/file stubs-dir file-name)]
(conj ret file))
ret))
ret
namespaces))
[]
namespaces))
(defn ^ShortenCommandLine shorten-stub-cmdline [^Module module]
(let [extension (.getInstance ClojureModuleExtension/Companion module)]
(.getShortenStubCommandLine extension)))
</clojure_code>
<clojure_code>
(ns cursive.scope)
(defn ^GlobalSearchScope module-with-deps-libs [^Module module]
(.getModuleWithDependenciesAndLibrariesScope module true))
</clojure_code>
Guidelines:
- Top-level Clojure functions should be converted to top-level Kotlin functions.
- When the code has documentation or comments, make sure you include those in the converted code.
- Be especially careful to maintain TODO comments in their original positions.
- Avoid the use of Kotlin sequences, including generateSequence.
You may see some functions which just delegate to interop calls. It is not necessary
to translate these functions, just use the interop at the original call site. Here is
an example, in this case you would replace calls to `(containing-file x)` with just
`x.getContainingFile()`.
<example>
<clojure_code>
(defn ^PsiFile containing-file [^PsiElement element]
(.getContainingFile element))
</clojure_code>
</example>
When there are map data structures that you are unsure how to translate, convert them to
`clojure.lang.Associative` instances, and access them using array access syntax with string
keys. So for example,
<example>
<clojure_code>
(defn print-highlighted [state message]
(let [{:keys [repl-state interrupt-printing]} @state
{:keys [project]} @repl-state]
...etc...)
</clojure_code>
</example>
Should be translated to:
<example>
<kotlin_code>
fun printHighlighted(state: Associative, message: String) {
val replState = state["repl-state"] as Associative
val project = replState["project"] as Project
val interruptPrinting = state["interrupt-printing"] as? AtomicBoolean
...etc...
}
</kotlin_code>
</example>
If the data structure appears to be a map inside an atom, convert them to `clojure.lang.Atom`
instances, and access them directly using array access syntax in the same way as Associative.
For both of these cases, you will need to use this import for the access syntax:
<example>
<kotlin_code>
import cursive.util.get
</kotlin_code>
</example>
If you are translating a variable or function whose name ends in '?', translate it to a name
starting with 'is', for example `string?` should be translated as `isString`.
If you are translating a small function with a single parameter, which just tests or accesses
a simple aspect of the parameter, translate that function to an extension property on the
type of the parameter. For example:
<example>
<clojure_code>
(defn ^VirtualFile virtual-file [^PsiElement element]
(if-let [file (.getContainingFile element)]
(.getVirtualFile (.getOriginalFile file))))
</clojure_code>
</example>
Should be translated to:
<example>
<kotlin_code>
val PsiElement.virtualFile: VirtualFile?
get() = containingFile?.originalFile?.virtualFile
</kotlin_code>
</example>
<example>
<clojure_code>
(defn significant?
[element]
(and (visible? element)
(not (meta? element))))
</clojure_code>
</example>
Should be translated to:
<example>
<kotlin_code>
val PsiElement.isSignificant: Boolean
get() = isVisible(element) && !isMeta(element)
</kotlin_code>
</example>
Translate this Clojure namespace to Kotlin. Create a whole Kotlin file, with the package corresponding to
the Clojure namespace.
Here is the namespace to translate:
<clojure_code>
(ns cursive.stubs
(:require [clojure.interop :refer [extend-class]]
[clojure.java.io :as io]
[clojure.set :as set]
[clojure.string :as str]
[cursive.application :as app]
[cursive.dumb :as dumb]
[cursive.index :as index]
[cursive.intellij.notify :as notify]
[cursive.language :as language]
[cursive.leiningen.exec :as exec]
[cursive.leiningen.module :as module]
[cursive.logging :as log]
[cursive.names :as names]
[cursive.progress :as progress]
[cursive.project :as project]
[cursive.scope :as scope]
[cursive.task :as task])
(:import (com.intellij.execution ExecutionException ShortenCommandLine)
(com.intellij.execution.configurations JavaParameters SimpleJavaParameters)
(com.intellij.execution.process OSProcessHandler ProcessAdapter ProcessOutputTypes)
(com.intellij.openapi.application PathManager)
(com.intellij.openapi.module Module ModuleManager)
(com.intellij.openapi.project IndexNotReadyException Project ProjectUtil)
(com.intellij.openapi.roots LibraryOrderEntry ModuleRootManager OrderRootType)
(com.intellij.openapi.util.io FileUtil FileUtilRt)
(com.intellij.openapi.vfs LocalFileSystem VirtualFile)
(cursive.config ClojureModuleExtension)
(cursive.leiningen LeiningenUtil)
(cursive.extensionPoints ModuleDirectoryFinder)
(cursive.leiningen LeiningenProjectSettings)
(cursive.leiningen.project LeinShimProvider)
(cursive.repl REPLUtil)
(cursive.runner UtilsKt)
(cursive.settings ClojureIdeSettings)
(cursive.shim IndicatorBridge)
(cursive.stubs ReplStubsNotifications ReplStubsSettings)
(java.io File)
(java.util.function Function)
(kotlin.jvm.functions Function1)))
(defn enabled? [^Module module]
(let [extension (.getInstance ClojureModuleExtension/Companion module)]
(.getCreateStubs extension)))
(defn warn-on-err? [^Module module]
(let [extension (.getInstance ClojureModuleExtension/Companion module)]
(.getWarnOnStderr extension)))
(defn ^ShortenCommandLine shorten-stub-cmdline [^Module module]
(let [extension (.getInstance ClojureModuleExtension/Companion module)]
(.getShortenStubCommandLine extension)))
(defn disable [^Module module]
(let [root-manager (ModuleRootManager/getInstance module)
modifiable (.getModifiableModel root-manager)
extension (.getModuleExtension modifiable ClojureModuleExtension)]
(.setCreateStubs ^ClojureModuleExtension extension false)
(app/with-write-action
(.commit modifiable))))
(defn disable-warn-on-err [^Module module]
(let [root-manager (ModuleRootManager/getInstance module)
modifiable (.getModifiableModel root-manager)
extension (.getModuleExtension modifiable ClojureModuleExtension)]
(.setWarnOnStderr ^ClojureModuleExtension extension false)
(app/with-write-action
(.commit modifiable))))
(defn find-files [roots path]
(eduction
(keep (fn [^VirtualFile file]
(.findFileByRelativePath file path)))
roots))
(defn ^String stub-file-name
"Generate a file name for the stubs for a namespace using a hash of the namespace
name, and the path to the virtual file containing it. The idea is to create a name
that will change if a library is updated. If no namespaces can be found, try to find
the namespace initialisation class instead, to handle binary-only libs (principally
Datomic). Returns nil if neither namespace nor class can be found."
([module namespace]
(stub-file-name module (language/strip-language namespace) (language/from-key namespace)))
([module namespace language]
(let [roots (into []
(comp
(filter #(instance? LibraryOrderEntry %))
(mapcat (fn [^LibraryOrderEntry entry]
(.getRootFiles entry OrderRootType/CLASSES)))
(filter #(FileUtilRt/extensionEquals (.getName ^VirtualFile %) "jar")))
(-> (ModuleRootManager/getInstance module)
(.getOrderEntries)))
base (-> namespace
(str/replace \- \_)
(str/replace \. \/))
lang-path (str base \. (name language))
cljc-path (str base ".cljc")
init-path (str base "__init.class")
source-files (eduction
cat
[(find-files roots lang-path)
(find-files roots cljc-path)])
files (if-not (empty? source-files)
source-files
(find-files roots init-path))]
; Only generate stubs for namespaces that we can find, see: https://github.com/cursive-ide/cursive/issues/2891
(when-not (empty? files)
(let [hash (reduce (fn [result vfile]
(unchecked-add-int
(unchecked-multiply-int (int 37) (int result))
(hash (.getCanonicalPath ^VirtualFile vfile))))
(hash namespace)
files)]
(str (Integer/toHexString hash) \. (name language)))))))
(defn ^String module-stub-path [^Project project ^Module module]
(str (PathManager/getSystemPath) \/
"cursive_stubs_" (.getLocationHash project) \/
(Integer/toHexString
(-> (or (some-> (ProjectUtil/guessModuleDir module)
(.getPath))
(names/name module))
(.hashCode)))))
(defn ^String module-tmp-path [project module]
(str (module-stub-path project module) "-tmp"))
(defn clj-namespace-form [project module namespace]
(when-let [file-name (stub-file-name module namespace :clj)]
(let [stubs-path (module-tmp-path project module)
stub-file-path (-> (File. stubs-path file-name)
(.getCanonicalPath))
existing-names (app/with-read-action
(set/union
(index/short-names-by-namespace project
namespace
(scope/module-with-deps-libs module)
:clj)
(index/short-names-by-namespace project
namespace
(scope/module-with-deps-libs module)
:cljc)))]
`(try
(println ~(str "Generating " namespace))
(spit ~stub-file-path
(let [ns-info# (cursive.stubs.metadata.clojure/read-ns (symbol ~namespace))]
(cursive.stubs.output/stub-string ns-info# ~existing-names)))
(catch Throwable t#
(.printStackTrace t#)
(System/exit 1))))))
(defn cljs-namespace-form [project module namespace]
(when-let [file-name (stub-file-name module namespace :cljs)]
(let [stubs-path (module-tmp-path project module)
stub-file-path (-> (File. stubs-path file-name)
(.getCanonicalPath))
existing-names (app/with-read-action
(set/union
(index/short-names-by-namespace project
namespace
(scope/module-with-deps-libs module)
:cljs)
(index/short-names-by-namespace project
namespace
(scope/module-with-deps-libs module)
:cljc)))]
`(try
(println ~(str "Generating " namespace))
(spit ~stub-file-path
(let [file# (str/replace (munge ~namespace) \. \/)
resource# (or (io/resource (str file# ".cljs"))
(io/resource (str file# ".cljc")))
ns-info# (cursive.stubs.metadata.clojurescript/read-namespace resource#)]
(cursive.stubs.output/stub-string ns-info# ~existing-names)))
(catch Throwable t#
(.printStackTrace t#)
(System/exit 1))))))
(defn finish-generation [project module]
(let [tmp-dir (File. (module-tmp-path project module))
final-dir (File. (module-stub-path project module))]
(FileUtil/delete final-dir)
(FileUtil/rename tmp-dir final-dir)
(app/invoke-later
(app/with-write-action
(let [fs (LocalFileSystem/getInstance)]
(when-let [dir (.refreshAndFindFileByIoFile fs final-dir)]
(->> dir
(.getChildren)
(into [])
(.refreshFiles fs))))))))
(defn add-runtime-jar [^SimpleJavaParameters params]
(let [runtime-jar-path (PathManager/getResourceRoot ^Class ReplStubsSettings "/cursive/stubs/output.clj")]
(-> params
(.getClassPath)
(.add (io/file runtime-jar-path)))))
(defn ^SimpleJavaParameters java-parameters [module ^File init-file]
(let [params (doto (JavaParameters.)
(.setMainClass "clojure.main")
(.setWorkingDirectory (if-let [virtual-file (.find ModuleDirectoryFinder/Companion module)]
(File. (.getCanonicalPath virtual-file))
(.getParentFile init-file)))
(.setDefaultCharset (project/from module)))
args (.getProgramParametersList params)]
(UtilsKt/configureJdkFromModule params module)
(REPLUtil/configureClasspath module params false)
(add-runtime-jar params)
(.add args (.getAbsolutePath init-file))
params))
(defn ^String escape-html
"Change special characters into HTML character entities."
[text]
(.. ^String text
(replace "&" "&amp;")
(replace "<" "&lt;")
(replace ">" "&gt;")
(replace "\"" "&quot;")))
(defn ^SimpleJavaParameters lein-parameters [^Module module ^File init-file]
(let [shim-provider (.getInstance LeinShimProvider/Companion)
project (project/from module)
settings (-> LeiningenProjectSettings/Companion
(.getInstance project))
version (-> LeiningenProjectSettings/Companion
(.currentVersion project))
indicator (progress/indicator)]
(if-let [result (.withShim shim-provider
project
version
(reify Function1
(invoke [_ shim]
(.apply ^Function shim
{"namespace" "cursive.leiningen.eval"
"var-name" "eval-details"
"arg" (merge (into {} (.standardArgs LeiningenUtil/INSTANCE))
{"project-file" (module/find-project-path module)
"script-file" (.getCanonicalPath ^File init-file)
"profiles" (str/join "," (.getSelectedProfiles settings))
"indicator" (IndicatorBridge. project indicator "Leiningen")})}))))]
(if-let [command (get result "command")]
(let [params (doto ^JavaParameters (exec/lein-parameters project command false)
(.configureByModule module JavaParameters/JDK_ONLY)
(.setWorkingDirectory (if-let [virtual-file (.find ModuleDirectoryFinder/Companion module)]
(File. (.getCanonicalPath virtual-file))
(.getParentFile init-file))))]
(add-runtime-jar params)
(.replaceOrAppend (.getVMParametersList params) "-agentlib:jdwp" "")
params)
(let [{:strs [exit-code message output error]} result
title (str "Leiningen aborted for module " (names/name module) (if exit-code (str ": exit code " exit-code)))
text (str (when message (str message "<br/>"))
"<pre>" (escape-html (str output)) "</pre>"
"<pre>" (escape-html (str error)) "</pre>")
group (-> ReplStubsNotifications/Companion
(.getNOTIFICATION_GROUP))]
(notify/error project group title text
[(notify/action (str "Disable stub generation for module " (names/name module))
(disable module))])
nil)))))
(defn all-stubs-files
"namespaces is {language -> #{namespace...}}"
[project module stubs-dir namespaces]
(reduce-kv (fn [ret lang namespaces]
(reduce (fn [ret namespace]
(if-let [file-name (stub-file-name module namespace lang)]
(let [file (io/file stubs-dir file-name)]
(conj ret file))
ret))
ret
namespaces))
[]
namespaces))
(defn create-script [project module clj-namespaces cljs-namespaces]
; This process is very prone to getting IndexNotReadyExceptions, try
; our best to retry if we get them, up to a point.
(let [ret (atom nil)]
(loop [count 0]
(if (>= count 5)
(throw (RuntimeException. "Unable to create stubs in 5 attempts"))
(let [done (promise)]
(dumb/when-smart project
(try
(reset! ret (str (when (seq clj-namespaces)
"(require 'cursive.stubs.metadata.clojure 'cursive.stubs.output)\n")
(str/join "\n" (->> clj-namespaces
(keep #(clj-namespace-form project module %))
(map pr-str)))
\newline
(when (seq cljs-namespaces)
"(require 'cursive.stubs.metadata.clojurescript 'cursive.stubs.output 'clojure.string 'clojure.java.io)\n")
(str/join "\n" (->> cljs-namespaces
(keep #(cljs-namespace-form project module %))
(map pr-str)))
\newline
"(System/exit 0)"))
(deliver done :done)
(catch IndexNotReadyException _
(deliver done :retry))
(catch Throwable ex
(deliver done ex))))
(let [result @done]
(if (instance? Throwable result)
(throw result)
(if-let [val @ret]
val
(do
(dumb/wait-till-smart project)
(recur (inc count)))))))))))
(defn generate-stubs
"Candidates is as returned by stubs-required"
[project candidates ^Runnable when-done]
(let [total (reduce + (map count (vals candidates)))]
(-> (task/backgroundable project "Generating stubs..."
(fn [indicator]
(progress/indeterminate indicator false)
(let [count (atom 0)
process (fn [items]
(cond
(progress/canceled? indicator) (.run when-done)
(not (seq items)) (.run when-done)
:else
(do
(progress/fraction indicator @count total)
(let [[module namespaces] (first items)]
(if (enabled? module)
(let [clj-namespaces (:clj namespaces)
cljs-namespaces (:cljs namespaces)
tmp-stubs-dir (File. (module-tmp-path project module))]
(progress/text indicator (str "Generating stubs for " (names/name module)))
(FileUtil/delete tmp-stubs-dir)
(.mkdirs tmp-stubs-dir)
(let [script-text (create-script project module clj-namespaces cljs-namespaces)
init-file (File/createTempFile "create-stub" ".clj")]
(spit init-file script-text)
(let [result (promise)
errors (atom [])
adapter (extend-class ProcessAdapter []
(processTerminated [this event]
(deliver result (.getExitCode event)))
(onTextAvailable [this event outputType]
(let [text (.getText event)]
(when (.startsWith text "Generating")
(progress/subtext indicator text)
(swap! count inc))
(when (and (= outputType ProcessOutputTypes/STDERR)
(not (.startsWith text "WARNING:")))
(swap! errors conj (str/trim text))))))]
(if-let [params (if (module/lein-module? module)
(lein-parameters module init-file)
(java-parameters module init-file))]
(let [_ (.setShortenCommandLine params (shorten-stub-cmdline module) project)
command-line (.toCommandLine params)
handler (OSProcessHandler. command-line)]
(log/debug "Generating stubs for " (names/name module)
" to " (.getCanonicalPath tmp-stubs-dir))
(doseq [namespace clj-namespaces]
(log/debug "Generating stubs for " namespace " (clj)"))
(doseq [namespace cljs-namespaces]
(log/debug "Generating stubs for " namespace " (cljs)"))
(log/debug "Script: " script-text)
(log/debug "Command line: " (.getCommandLineString command-line))
(.addProcessListener handler adapter)
(.startNotify handler)
@result
(dumb/when-smart project
(let [all-generated? (reduce (fn [ret ^File file]
(if (.exists file)
ret
(do
(log/debug "File "
(.getAbsolutePath file)
" not generated")
false)))
true
(all-stubs-files project module tmp-stubs-dir namespaces))]
(finish-generation project module)
(let [exit-code @result]
(when (and (or (seq @errors)
(not (zero? exit-code)))
(or (not all-generated?)
(warn-on-err? module)))
(notify/warn project
(-> ReplStubsNotifications/Companion
(.getNOTIFICATION_GROUP))
(str "WARNING: Error "
(when (seq @errors) "output ")
"generating stubs for module " (names/name module)
(when (not all-generated?)
" (not all files generated)"))
(str (when-not (zero? exit-code)
(str "Exit code: " exit-code "<br/>"))
(when (seq @errors)
(str "<pre>"
(str/join "\n" @errors)
"</pre>")))
[(notify/action (str "Disable stub generation for module " (names/name module))
#(disable module))
(notify/action (str "Don't warn me about this again for module " (names/name module))
#(disable-warn-on-err module))])
(let [settings (ClojureIdeSettings/getInstance)]
(set! (.-alwaysGenerateStubs settings) false)))))))
(log/debug "No params for " (names/name module)))
(recur (rest items)))))
(log/debug "Stub generation disabled for " (names/name module)))))))]
(try
(process candidates)
(catch ExecutionException ex
(notify/error project
(-> ReplStubsNotifications/Companion
(.getNOTIFICATION_GROUP))
"Stub execution failed: "
(str (.getName (class ex)) "<br/>"
(or (.getMessage ex) ex))))))))
(task/queue))))
(defn stubs-required
"Checks whether stubs are required to be refreshed for this project.
stub-namespaces contains namespace names with language, i.e. clj:datomic.api.
If some are required, returns {module -> {language -> #{namespace...}}}"
[project stub-namespaces]
(let [modules (-> (ModuleManager/getInstance project)
(.getModules))
stub-namespaces (set stub-namespaces)]
(reduce (fn [res module]
(log/debug "Checking module " (names/name module))
(if (enabled? module)
(let [scope (scope/module-with-deps-libs module)
path (module-stub-path project module)
dependencies (app/with-read-action
(->> (index/namespace-names scope)
(mapcat #(keys (index/dependencies % scope)))
(into #{})))
stub-dependencies (filter stub-namespaces dependencies)
_ (log/debug "Stub dependencies: " (str/join ", " stub-dependencies))
need-stubs (reduce (fn [res dep]
(if-let [file-name (stub-file-name module dep)]
(assoc res dep file-name)
res))
{}
stub-dependencies)
_ (log/debug "Need stubs: " (str/join ", " (keys need-stubs)))]
(if (some #(not (.exists (io/file path %)))
(vals need-stubs))
(do
(log/debug "Stub generation required")
(assoc res module (reduce (fn [res namespace]
(let [language (language/from-key namespace)
namespace (language/strip-language namespace)]
(assoc res language (conj (get res language #{}) namespace))))
(get res module {})
(keys need-stubs))))
(do
(log/debug "Stub generation not required")
res)))
(log/debug "Stub generation disabled for " (names/name module))))
{}
modules)))
</clojure_code>
The most important things to remember are:
1. **Remember to maintain the comments from the original code, especially TODO comments.**
2. **Inline small interop wrapper methods directly into the call sites.**
3. **Remember to keep the package the same as the original namespace name, when translating whole files.**
4. **Provide the result as an artifact.**
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment