├── .clj-kondo ├── babashka │ ├── fs │ │ └── config.edn │ └── sci │ │ ├── config.edn │ │ └── sci │ │ └── core.clj ├── http-kit │ └── http-kit │ │ ├── config.edn │ │ └── httpkit │ │ └── with_channel.clj ├── rewrite-clj │ └── rewrite-clj │ │ └── config.edn └── taoensso │ └── encore │ ├── config.edn │ └── taoensso │ └── encore.clj ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bb.edn ├── build.edn ├── deps.edn ├── src └── com │ └── xadecimal │ ├── expose_api.clj │ └── expose_api │ └── impl.clj └── test └── com └── xadecimal └── expose_api └── impl_test.clj /.clj-kondo/babashka/fs/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/babashka/sci/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks {:macroexpand {sci.core/copy-ns sci.core/copy-ns}}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/babashka/sci/sci/core.clj: -------------------------------------------------------------------------------- 1 | (ns sci.core) 2 | 3 | (defmacro copy-ns 4 | ([ns-sym sci-ns] 5 | `(copy-ns ~ns-sym ~sci-ns nil)) 6 | ([ns-sym sci-ns opts] 7 | `[(quote ~ns-sym) 8 | ~sci-ns 9 | (quote ~opts)])) 10 | -------------------------------------------------------------------------------- /.clj-kondo/http-kit/http-kit/config.edn: -------------------------------------------------------------------------------- 1 | 2 | {:hooks 3 | {:analyze-call {org.httpkit.server/with-channel httpkit.with-channel/with-channel}}} 4 | -------------------------------------------------------------------------------- /.clj-kondo/http-kit/http-kit/httpkit/with_channel.clj: -------------------------------------------------------------------------------- 1 | (ns httpkit.with-channel 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn with-channel [{node :node}] 5 | (let [[request channel & body] (rest (:children node))] 6 | (when-not (and request channel) (throw (ex-info "No request or channel provided" {}))) 7 | (when-not (api/token-node? channel) (throw (ex-info "Missing channel argument" {}))) 8 | (let [new-node 9 | (api/list-node 10 | (list* 11 | (api/token-node 'let) 12 | (api/vector-node [channel (api/vector-node [])]) 13 | request 14 | body))] 15 | 16 | {:node new-node}))) 17 | -------------------------------------------------------------------------------- /.clj-kondo/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /.clj-kondo/taoensso/encore/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks {:analyze-call {taoensso.encore/defalias taoensso.encore/defalias}}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/taoensso/encore/taoensso/encore.clj: -------------------------------------------------------------------------------- 1 | (ns taoensso.encore 2 | (:require 3 | [clj-kondo.hooks-api :as hooks])) 4 | 5 | (defn defalias [{:keys [node]}] 6 | (let [[sym-raw src-raw] (rest (:children node)) 7 | src (if src-raw src-raw sym-raw) 8 | sym (if src-raw 9 | sym-raw 10 | (symbol (name (hooks/sexpr src))))] 11 | {:node (with-meta 12 | (hooks/list-node 13 | [(hooks/token-node 'def) 14 | (hooks/token-node (hooks/sexpr sym)) 15 | (hooks/token-node (hooks/sexpr src))]) 16 | (meta src))})) 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *.jar 4 | *.class 5 | /lib/ 6 | /classes/ 7 | /target/ 8 | /checkouts/ 9 | .lein-deps-sum 10 | .lein-repl-history 11 | .lein-plugins/ 12 | .lein-failures 13 | .nrepl-port 14 | .cpcache/ 15 | .clj-kondo/.cache/ 16 | .lsp/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | Types of changes: 9 | - Added for new features. 10 | - Changed for changes in existing functionality. 11 | - Deprecated for soon-to-be removed features. 12 | - Removed for now removed features. 13 | - Fixed for any bug fixes. 14 | - Security in case of vulnerabilities. 15 | 16 | ## [Unreleased] 17 | 18 | ### Added 19 | - Updated `README.md` to include info and usage examples for `expose-vars` macro. 20 | 21 | ## [0.3.0] - 2024-07-11 22 | 23 | ### Added 24 | 25 | - expose-vars macro that lets you expose vars in the current namespace as a macro instead of through using source code generation. This serves as an alternative to code-gen for those who prefer that. [#3](https://github.com/xadecimal/expose-api/issues/3) 26 | 27 | ## [0.2.0] - 2024-07-08 28 | 29 | ### Added 30 | 31 | - Generated code is backtranslated so that quote shows as ' and so on. 32 | 33 | ### Fixed 34 | 35 | - Multiline comments are now handled properly, so they'll show on multiple lines in the generated source. [#1](https://github.com/xadecimal/expose-api/issues/1) 36 | 37 | ## [0.1.0] - 2024-07-07 38 | 39 | ### Added 40 | 41 | - Initial release 42 | - expose-api function can be used to generate a public API namespace from impl vars. 43 | - Doc and arities are copied over from the impl defn/defmacro. 44 | - Support both defn and defnmacro. 45 | - Support & rest var-args. 46 | - Support multi-arity. 47 | - Generated source file includes warning comment block to warn not to hand modify file. 48 | 49 | [unreleased]: https://github.com/xadecimal/expose-api/compare/0.3.0...HEAD 50 | [0.3.0]: https://github.com/xadecimal/expose-api/compare/0.2.0...0.3.0 51 | [0.2.0]: https://github.com/xadecimal/expose-api/compare/0.1.0...0.2.0 52 | [0.1.0]: https://github.com/xadecimal/expose-api/tree/0.1.0 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 xadecimal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # expose-api 2 | 3 | `expose-api` is a Clojure library designed to simplify the process of creating public-facing API namespaces. In Clojure, it's common to define a namespace as your "public" interface and have implementation namespaces with the actual code. Manually writing the public namespace can be tedious. This library automates that process by generating a Clojure namespace that wraps implementation vars and exposes a public API. 4 | 5 | ## Features 6 | 7 | - Generate a public API namespace from a specified namespace declaration and a list of implementation vars. 8 | - Maintain consistent documentation and arities for the exposed functions and macros. 9 | - Generates Clojure source code, leaving the runtime in a clean state. 10 | 11 | ## Installation 12 | 13 | ### Leiningen 14 | 15 | Add the following dependency to your `project.clj`: 16 | 17 | ```clojure 18 | [com.xadecimal/expose-api "0.3.0"] 19 | ``` 20 | 21 | ### Clojure CLI/deps.edn 22 | 23 | Add the following dependency to your `deps.edn`: 24 | 25 | ```clojure 26 | {:deps {com.xadecimal/expose-api {:mvn/version "0.3.0"}}} 27 | ``` 28 | 29 | ## Usage 30 | 31 | ### Example 32 | 33 | Here's a quick example of how to use `expose-api` to generate a public API namespace: 34 | 35 | ```clojure 36 | (ns your.namespace 37 | (:require [com.xadecimal.expose-api :as api])) 38 | 39 | (api/expose-api 40 | :file-path "./src/com/xadecimal/my_lib.clj" 41 | :ns-code `(~'ns ~'com.xadecimal.my-lib 42 | "A very cool library which lets you do cool things. 43 | To use it, require it and call its cool functions." 44 | (:refer-clojure :exclude ~'[defn]) 45 | (:require ~'[com.xadecimal.my-lib.impl :as impl])) 46 | :vars [#'impl/defn #'impl/cool]) 47 | ``` 48 | 49 | This will create a file `./src/com/xadecimal/my_lib.clj` containing Clojure source code of the specified namespace form, and with a function named `cool` which calls the `impl/cool` function, with the same doc and arities as that of the `impl/cool` function, as well as a macro named `defn` which calls the `impl/defn` macro, with the same doc and arities as that of the `impl/defn` macro. 50 | 51 | ### Parameters 52 | 53 | - `file-path` - A string pointing to the path where you want the generated `.clj` namespace source file to be created. This should match the `ns-code` namespace path. It's relative to where you are executing from. 54 | - `ns-code` - An `ns` code form, which will become the `ns` directive in the generated namespace source code. Ensure that you require the implementation namespaces that the vars use. 55 | - `vars` - A sequence of vars that you want to wrap and expose publicly in the generated namespace. Vars are assumed to come from implementation namespaces. 56 | 57 | ## Usage as a macro 58 | 59 | In 0.3.0, expose-api also lets you expose your vars using a macro: `expose-vars`, which won't require any source code generation. It's more straightforward, but since the generation happens at compile time, tooling that read static sources won't work with it, such as clj-kondo and clojure-lsp. Cider will pick it up when connected to a REPL. 60 | 61 | ### Example 62 | 63 | In the namespace you want as your public API: 64 | 65 | ```clojure 66 | (ns com.xadecimal.my-lib 67 | "A very cool library which lets you do cool things. 68 | To use it, require it and call its cool functions." 69 | (:refer-clojure :exclude [defn]) 70 | (:require [com.xadecimal.expose-api :refer [expose-vars]] 71 | [com.xadecimal.my-lib.impl :as impl])) 72 | 73 | (expose-vars [#'impl/defn #'impl/cool]) 74 | ``` 75 | 76 | You don't need a build step when using `expose-vars`, since unlike `expose-api`, it doesn't generate source code, but will instead generate at macro-expansion time. 77 | 78 | ## Comparison with Potemkin's import-vars 79 | 80 | Potemkin's `import-vars` is another tool that facilitates the creation of public APIs by importing vars from other namespaces. Here's a comparison of `expose-api` and Potemkin's `import-vars`: 81 | 82 | ### Potemkin's import-vars 83 | 84 | - **Usage**: Potemkin's `import-vars` is used directly in the namespace definition to import vars from other namespaces. 85 | - **Syntax**: 86 | ```clojure 87 | (ns your.namespace 88 | (:require [potemkin :refer [import-vars]] 89 | [some.other.namespace :as other])) 90 | 91 | (import-vars other/foo other/bar) 92 | ``` 93 | - **Pros**: Simple and straightforward for importing vars. 94 | - **Cons**: Monkey-patches things at runtime, which can lead to a less clean runtime state. 95 | 96 | ### expose-api 97 | 98 | - **Usage**: `expose-api` generates a new namespace file with the specified public API, wrapping implementation vars. 99 | - **Syntax**: 100 | ```clojure 101 | (api/expose-api 102 | :file-path "./src/com/xadecimal/my_lib.clj" 103 | :ns-code `(~'ns ~'com.xadecimal.my-lib 104 | "A very cool library which lets you do cool things." 105 | (:refer-clojure :exclude ~'[defn]) 106 | (:require ~'[com.xadecimal.my-lib.impl :as impl])) 107 | :vars [#'impl/defn #'impl/cool]) 108 | ``` 109 | - **Pros**: Generates Clojure source code, leaving the runtime in a better state. Automates the tedious process of copying over arities and doc-strings. 110 | - **Cons**: Requires an additional step to generate the namespace file. 111 | 112 | ### expose-vars 113 | 114 | - **Usage**: `expose-vars` is used directly in the namespace definition to expose vars from other namespaces, same as Potemkin's `import-vars`. 115 | - **Syntax**: 116 | ```clojure 117 | (api/expose-vars [#'impl/defn #'impl/cool]) 118 | ``` 119 | - **Pros**: As simple and straightforward for exposing vars as Potemkin's `import-vars`, but without monkey-patching, thus it leaves the runtime in a clean state. 120 | - **Cons**: Not friendly to static analyzers like clj-kondo or clojure-lsp. 121 | 122 | ## Build Step Integration 123 | 124 | It is recommended to use `expose-api` from a build step. Here’s an example of how you can add such a build step using `tools.build`: 125 | 126 | ### tools.build Example 127 | 128 | 1. Create a build script (e.g., `build.clj`): 129 | 130 | ```clojure 131 | (ns build 132 | (:require [clojure.tools.build.api :as b] 133 | [com.xadecimal.expose-api :as api])) 134 | 135 | (defn generate-api [m] 136 | (api/expose-api 137 | :file-path "./src/com/xadecimal/my_lib.clj" 138 | :ns-code `(~'ns ~'com.xadecimal.my-lib 139 | "A very cool library which lets you do cool things." 140 | (:refer-clojure :exclude ~'[defn]) 141 | (:require ~'[com.xadecimal.my-lib.impl :as impl])) 142 | :vars [#'impl/defn #'impl/cool])) 143 | ``` 144 | 145 | 2. Add a build alias to your `deps.edn` 146 | 147 | ```clojure 148 | :build {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.10.4" :git/sha "31388ff"} 149 | com.xadecimal/expose-api {:mvn/version "0.3.0"}} 150 | :extra-paths ["."] 151 | :ns-default build} 152 | ``` 153 | 154 | You need it to include your project's source whose vars are getting exposed. This is why we use `:extra-deps` and `:extra-paths`, because we will run it with `-X` and not as a tool. 155 | 156 | 3. Run the build script: 157 | 158 | ```shell 159 | clojure -X:build generate-api 160 | ``` 161 | 162 | This will generate the specified public API namespace as part of your build process, ensuring that your API is always up to date with the latest implementation changes. 163 | 164 | ## License 165 | 166 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 167 | 168 | ## Acknowledgments 169 | 170 | Thanks to the Clojure community for their support and contributions to the ecosystem. 171 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {test (clojure "-X:test") 3 | lint (clojure "-T:build lint") 4 | deploy (clojure "-T:build deploy") 5 | install (clojure "-T:build install") 6 | update-documents (clojure "-T:build update-documents") 7 | bump-major-version (clojure "-T:build bump-major-version") 8 | bump-minor-version (clojure "-T:build bump-minor-version") 9 | bump-patch-version (clojure "-T:build bump-patch-version") 10 | release (do (run 'lint) 11 | (run 'test) 12 | (run 'install)) 13 | publish {:requires ([clojure.edn :as edn]) 14 | :task (let [version (-> (edn/read-string (slurp "build.edn")) :version)] 15 | (run 'release) 16 | (run 'update-documents) 17 | (shell "git add .") 18 | (try (shell "git diff --quiet --cached") 19 | (catch Exception _ 20 | (shell 21 | (str "git commit -m \"Prepared for new release \"" version)))) 22 | (shell "git push") 23 | (shell (str "git tag -a " version " -m \"Release v\"" version)) 24 | (shell (str "git push origin " version)) 25 | (run 'deploy))} 26 | publish-patch (do (run 'bump-patch-version) 27 | (run 'publish)) 28 | publish-minor (do (run 'bump-minor-version) 29 | (run 'publish)) 30 | publish-major (do (run 'bump-major-version) 31 | (run 'publish))}} 32 | -------------------------------------------------------------------------------- /build.edn: -------------------------------------------------------------------------------- 1 | {:lib com.xadecimal/expose-api 2 | :version "0.3.0" 3 | :description "A Clojure library to automatically generate public API namespaces by wrapping and exposing functions and macros from implementation namespaces." 4 | :licenses [{:name "MIT License" 5 | :url "https://github.com/xadecimal/expose-api/blob/main/LICENSE"}] 6 | :documents [{:file "README.md" 7 | :match ":deps \\{com\\.xadecimal\\/expose-api" 8 | :action :replace 9 | :keep-indent? true 10 | :text "{:deps {com.xadecimal/expose-api {:mvn/version \"{{version}}\"}}}"} 11 | {:file "README.md" 12 | :match "\\[com\\.xadecimal\\/expose-api" 13 | :action :replace 14 | :keep-indent? true 15 | :text "[com.xadecimal/expose-api \"{{version}}\"]"}]} 16 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :deps {org.clojure/clojure {:mvn/version "1.11.3"} 3 | zprint/zprint {:mvn/version "1.2.9"}} 4 | :aliases 5 | {:test {:extra-paths ["test"] 6 | :extra-deps {io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"}} 7 | :exec-fn cognitect.test-runner.api/test 8 | :exec-args {:dirs ["test"] 9 | :patterns ["com.xadecimal.*"]}} 10 | :build {:deps {com.github.liquidz/build.edn {:mvn/version "0.11.257"}} 11 | :ns-default build-edn.main}}} 12 | -------------------------------------------------------------------------------- /src/com/xadecimal/expose_api.clj: -------------------------------------------------------------------------------- 1 | (ns com.xadecimal.expose-api 2 | (:require [com.xadecimal.expose-api.impl :as impl])) 3 | 4 | (defn expose-api 5 | "Generate a public facing API namespace. 6 | 7 | Takes a map of: 8 | 9 | * file-path - a string pointing to the path where you want the generated .clj 10 | namespace source file to be created, should match the ns-code 11 | namespace path. Relative to where you are executing from. 12 | * ns-code - An ns code form, which will become the ns directive in the 13 | generated namespace source code. Make sure to require the impl 14 | namespaces the vars make use of. 15 | * vars - A seqable of vars which you want wrapped and exposed publicly in 16 | the generated namespace. Vars are assume to come from impl 17 | namespaces. 18 | 19 | Example: 20 | (expose-api :file-path \"./src/com/xadecimal/my_lib.clj\" 21 | :ns-code `(~'ns ~'com.xadecimal.my-lib 22 | \"A very cool library which let's you do cool things. 23 | To use it, require it and call it's cool functions.\" 24 | (:refer-clojure :exclude ~'[defn]) 25 | (:require ~'[com.xadecimal.my-lib.impl :as impl])) 26 | :vars [#'impl/defn #'impl/cool]) 27 | 28 | This will create the file ./src/com/xadecimal/my_lib.clj which will 29 | contain Clojure source code of the specified ns form, and with a function 30 | named cool which calls the impl/cool function, with the same doc and 31 | arities as that of the impl/cool function, as well as a macro named defn 32 | which calls the impl/defn macro, with the same doc and arities as that of 33 | the impl/defn macro." 34 | [& {:keys [file-path ns-code vars]}] 35 | (let [ns-str (impl/gen-str ns-code) 36 | vars (mapv #(vector % []) vars) 37 | top-level-forms-str (mapv #(impl/gen-top-level-str (first %) (second %)) vars)] 38 | (impl/spit-generated-file file-path ns-str top-level-forms-str))) 39 | 40 | (defmacro expose-vars 41 | "Use in an existing namespace, will defn or defmacro wrapper functions or 42 | macros that have the same signature and doc-string as the ones from the 43 | given vars, and which internally delegates to the vars fn/macro. 44 | 45 | Unlike expose-api, this does not perform source code generation, but is 46 | meant to be used as a macro. Making it more convenient, but it won't expose 47 | things in a way that is friendly to human reader or static analysis tools. 48 | 49 | Take a seqable of vars: 50 | 51 | * vars - A seqable of vars which you want wrapped and exposed publicly in 52 | the current namespace. Vars are assume to come from impl 53 | namespaces. 54 | 55 | Example: 56 | (ns foo 57 | (:require [com.xadecimal.expose-api :refer [expose-vars]])) 58 | 59 | (expose-vars [#'impl/defn #'impl/cool]) 60 | 61 | This will define a macro called defn and a function called cool in the foo 62 | namespace which have the same arity, signature and doc-string as impl/defn 63 | and impl/cool. When called, they will internally delegate to impl/defn and 64 | impl/cool." 65 | [vars] 66 | (let [vars (mapv #(vector (requiring-resolve (second %)) []) vars) 67 | top-level-forms (->> vars 68 | (map #(impl/gen-top-level-str (first %) (second %))) 69 | (mapv read-string))] 70 | `(do ~@top-level-forms))) 71 | -------------------------------------------------------------------------------- /src/com/xadecimal/expose_api/impl.clj: -------------------------------------------------------------------------------- 1 | (ns com.xadecimal.expose-api.impl 2 | (:require [zprint.core :as zp] 3 | [clojure.string :as str])) 4 | 5 | (def gen-warning (str/triml " 6 | ;;;; DO NOT MODIFY ;;;; 7 | ;;; WARNING ;;; 8 | 9 | ;; This class was auto-generated by expose-api, do not manually edit 10 | ;; or your edits might get overwritten when it gets re-generated. 11 | ;; Instead modify the expose-api generation. 12 | ;; Refer to https://github.com/xadecimal/expose-api 13 | 14 | ;;; WARNING ;;; 15 | ;;;; DO NOT MODIFY ;;; 16 | ")) 17 | 18 | (defn gen-doc 19 | [mta] 20 | (when-let [doc (:doc mta)] [doc])) 21 | 22 | (defn gen-norm-parameters 23 | [norm-args splice?] 24 | (mapv #(if splice? (symbol (str "~" %)) %) norm-args)) 25 | 26 | (defn gen-rest-parameters 27 | [norm-args rest-arg splice?] 28 | (conj 29 | (mapv #(if splice? (symbol (str "~" %)) %) norm-args) 30 | (if splice? (symbol (str "~@" rest-arg)) rest-arg))) 31 | 32 | (defn gen-call 33 | [vr args macro?] 34 | (let [[norm-args [_ rest-arg]] (split-with (complement #{'&}) args)] 35 | (if (nil? rest-arg) 36 | (if macro? 37 | (cons (symbol "`") `((~(symbol vr) ~@(gen-norm-parameters norm-args true)))) 38 | [`(~(symbol vr) ~@(gen-norm-parameters norm-args false))]) 39 | (if macro? 40 | (cons (symbol "`") `((~(symbol vr) ~@(gen-rest-parameters norm-args rest-arg true)))) 41 | [`(apply ~(symbol vr) ~@(gen-rest-parameters norm-args rest-arg false))])))) 42 | 43 | (defn gen-macro-call 44 | [vr mta] 45 | (for [args (:arglists mta)] 46 | `(~args 47 | ~@(gen-call vr args true)))) 48 | 49 | (defn gen-macro 50 | [vr mta copy-meta] 51 | `(~'defmacro ~(:name mta) 52 | ~@(gen-doc mta) 53 | ~(select-keys mta copy-meta) 54 | ~@(gen-macro-call vr mta))) 55 | 56 | (defn gen-defn 57 | [vr mta copy-meta] 58 | `(~'defn ~(:name mta) 59 | ~@(gen-doc mta) 60 | ~(assoc (select-keys mta copy-meta) 61 | :inline `(~'fn ~@(gen-macro-call vr mta))) 62 | ~@(for [args (:arglists mta)] 63 | `(~args 64 | ~@(gen-call vr args false))))) 65 | 66 | (defn gen-str 67 | [forms] 68 | (str/replace 69 | (with-out-str 70 | (zp/zprint forms {:style :backtranslate})) 71 | #"\\r\\n|\\r|\\n" 72 | "\n")) 73 | 74 | (defn gen-top-level 75 | [vr copy-meta] 76 | (let [mta (meta vr)] 77 | (if (:macro mta) 78 | (gen-macro vr mta copy-meta) 79 | (gen-defn vr mta copy-meta)))) 80 | 81 | (defn gen-top-level-str 82 | [vr copy-meta] 83 | (gen-str (gen-top-level vr copy-meta))) 84 | 85 | (defn spit-generated-file 86 | [file-path ns-str top-level-forms] 87 | (spit file-path 88 | (str/join 89 | \newline 90 | (conj 91 | (into [gen-warning ns-str] top-level-forms) 92 | gen-warning)))) 93 | -------------------------------------------------------------------------------- /test/com/xadecimal/expose_api/impl_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.xadecimal.expose-api.impl-test 2 | (:require [com.xadecimal.expose-api.impl :as impl] 3 | [clojure.test :refer [deftest is]])) 4 | 5 | (defn testivius 6 | "This is a 7 | multiline docstring." 8 | ([a] a) 9 | ([b c] [b c])) 10 | 11 | (deftest gen-doc 12 | (is (= "This is a\n multiline docstring." 13 | (first (impl/gen-doc (meta #'testivius)))))) 14 | 15 | (deftest gen-str 16 | (is (= "(defn foo [a] 'a)\n" 17 | (impl/gen-str 18 | '(defn foo [a] (quote a))))) 19 | (is (= "(defn foo \"One\n Two\" [a] 'a)\n" 20 | (impl/gen-str 21 | '(defn foo "One 22 | Two" [a] (quote a)))))) 23 | --------------------------------------------------------------------------------