Avi Flax
Avi Flax

Reputation: 51849

How can I create a single Clojure source file which can be safely used as a script and a library without AOT compilation?

I’ve spent some time researching this and though I’ve found some relevant info,

Here’s what I’ve found:

but none of them have answered the question satisfactorily.

My Clojure source code file defines a namespace and a bunch of functions. There’s also a function which I want to be invoked when the source file is run as a script, but never when it’s imported as a library.

So: now that it’s 2012, is there a way to do this yet, without AOT compilation? If so, please enlighten me!

Upvotes: 1

Views: 569

Answers (3)

algal
algal

Reputation: 28104

This might be helpful: the github project lein-oneoff describes itself as "dependency management for one-off, single-file clojure programs."

This lets you define everything in one file, but you do need the oneoff plugin installed in order to run it from the command line.

Upvotes: 1

mikera
mikera

Reputation: 106401

I'm assuming by run as a script you mean via clojure.main as follows:

java -cp clojure.jar clojure.main /path/to/myscript.clj

If so then there is a simple technique: put all the library functions in a separate namespace like mylibrary.clj. Then myscript.clj can use/require this library, as can your other code. But the specific functions in myscript.clj will only get called when it is run as a script.

As a bonus, this also gives you a good project structure, as you don't want script-specific code mixed in with your general library functions.

EDIT:

I don't think there is a robust within Clojure itself way to determine whether a single file was launched as a script or loaded as a library - from Clojure's perspective, there is no difference between the two (it all gets loaded in the same way via Compiler.load(...) in the Clojure source for anyone interested).

Options if you really want to detect the manner of the launch:

  • Write a main class in Java which sets a static flag then launched the Clojure script. You can easily test this flag from Clojure.
  • Use AOT compilation to implement a Clojure main class which sets a flag
  • Use *command-line-args* to indicate script usage. You'll need to pass an extra parameter like "script" on the command line.
  • Use a platform-specific method to determine the command line (e.g. from the environment variables in Windows)
  • Use the --eval option in the clojure.main command line to load your clj file and launch a specific function that represents your script. This function can then set a script-specific flag if needed
  • Use one of the methods for detecting the Java main class at runtime

Upvotes: 2

Avi Flax
Avi Flax

Reputation: 51849

I’ve come up with an approach which, while deeply flawed, seems to work.

I identify which namespaces are known when my program is running as a script. Then I can compare that number to the number of namespaces known at runtime. The idea is that if the file is being used as a lib, there should be at least one more namespace present than in the script case.

Of course, this is extremely hacky and brittle, but it does seem to work:

(defn running-as-script
    "This is hacky and brittle but it seems to work. I’d love a better
    way to do this; see http://stackoverflow.com/q/9027265"
    []
    (let
        [known-namespaces
            #{"clojure.set"
                "user"
                "clojure.main" 
                "clj-time.format" 
                "clojure.core" 
                "rollup" 
                "clj-time.core" 
                "clojure.java.io" 
                "clojure.string" 
                "clojure.core.protocols"}]
        (= (count (all-ns)) (count known-namespaces))))

Upvotes: 1

Related Questions