{"id":16284112,"url":"https://github.com/emlyn/tortilla","last_synced_at":"2025-03-16T13:31:24.902Z","repository":{"id":41634854,"uuid":"222952936","full_name":"emlyn/tortilla","owner":"emlyn","description":"A thin Clojure wrapper for Java APIs providing efficient and idiomatic interop.","archived":false,"fork":false,"pushed_at":"2023-07-22T17:25:01.000Z","size":187,"stargazers_count":47,"open_issues_count":10,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-15T03:14:41.285Z","etag":null,"topics":["clojure","java","metaprogramming","reflection"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/emlyn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-11-20T14:14:09.000Z","updated_at":"2025-02-02T17:00:26.000Z","dependencies_parsed_at":"2024-10-27T10:51:50.744Z","dependency_job_id":"307f8241-b34c-41a7-8492-622ae0c8892a","html_url":"https://github.com/emlyn/tortilla","commit_stats":{"total_commits":180,"total_committers":3,"mean_commits":60.0,"dds":0.01666666666666672,"last_synced_commit":"1a30ac21c0d4d214ee43646aa38ecc0825c7df14"},"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emlyn%2Ftortilla","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emlyn%2Ftortilla/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emlyn%2Ftortilla/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/emlyn%2Ftortilla/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/emlyn","download_url":"https://codeload.github.com/emlyn/tortilla/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243817119,"owners_count":20352501,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","java","metaprogramming","reflection"],"created_at":"2024-10-10T19:18:22.617Z","updated_at":"2025-03-16T13:31:24.551Z","avatar_url":"https://github.com/emlyn.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# tortilla - a thin Clojure wrapper for Java classes\n\n\u003e My mom cooked the same food every day - tortillas, beans and meat.\n\u003e If it was enchiladas, it was - tortillas, beans and meat.\n\u003e If it was burritos, it was still - tortillas, beans and meat.\\\n\u003e \u0026nbsp; \u0026nbsp; \u0026nbsp; \u0026nbsp; — *Felipe Esparza*\n\n[![Build](https://github.com/emlyn/tortilla/workflows/Build/badge.svg)](https://github.com/emlyn/tortilla/actions?query=workflow%3ABuild)\n[![Coverage Status](https://coveralls.io/repos/github/emlyn/tortilla/badge.svg?branch=HEAD)](https://coveralls.io/github/emlyn/tortilla?branch=HEAD)\n[![Dependencies Status](https://versions.deps.co/emlyn/tortilla/status.svg)](https://versions.deps.co/emlyn/tortilla)\n[![Downloads](https://img.shields.io/clojars/dt/emlyn/tortilla.svg)](https://clojars.org/emlyn/tortilla)\n\n## Warning\n\nThis library is very experimental, and the current implementation is not efficient.\nIn fact, IT IS SLOWER THAN NORMAL UNHINTED INTEROP CODE (see [comment](https://github.com/emlyn/tortilla/issues/20#issuecomment-1646567785)), so it is not recommended for use at the moment.\n\n## Introduction\n\nInterfacing to Java libraries from Clojure can be ugly.\nYou have to use the special interop syntax,\nand either sprinkle type hints all over the place,\nor risk having runtime reflection killing performance.\n\nCalling vararg methods is also clumsy, as normal Java interop in Clojure\ndoesn't have any special handling for them, so you have to manually wrap\nthe variable arguments in a Java array using e.g. `into-array`.\n\nJava functions also tend to expect different types which need special handling to convert the usual Clojure values into the exoected types.\nFor example functions that expect an `Integer` or a `Float` need the argument to be wrapped in `(int x)` or `(float y)`, those that expect an array need a Clojure vector or list to be wrapped in `(into-array TYPE my_vec)`.\nAdditionally, in Java 8 there are Funcional Interfaces (that accept a lambda expression in Java), but from Clojure you need to wrap the Clojure function in `proxy` or `reify` to convert it to the right type.\n\nTortilla aims to remedy this by using reflection at compile time to\nautomatically generate reflection-free idiomatic Clojure function wrappers\naround Java class methods.\nThese wrappers then know the parameter types of the various candidate overloads and can select the correct one with simple type checks that are much faster than full runtime reflection.\nThey provide variable-arity functions to wrap vararg methods so that\nthey can be called idiomatically from Clojure.\nTortilla also includes support for automatically coercing Clojure types.\nSo, for example, you can just pass normal Clojure longs and doubles, and tortilla will coerce them to Integer and Float respectively, when necessary.\nYou can also pass in a Clojure vector when an array is excepcted, or a Clojure function when a Functional Interface is expected (e.g. `java.lang.function.Function`, `java.io.FileFilter`...)\n\nTortilla is still in the early alpha stages of development\nand has not yet been heavily tested,\nso it is not reecommended for use in production code.\nHowever, the basic functionality is mostly in place,\nso feel free to give it a try, and provide feedback\nif you find anything that doeesn't work,\nor any functionality that's missing.\n\n## Usage\n\nThere are two ways to use Tortilla: either as a macro that you call in your code to\ngenerate the wrapper functions at compile time, or as a command-line interface (CLI)\nthat you run as a separate step to generate Clojure source files that you then build\nwith the rest of your code. There are pros and cons to both approaches:\n\n- Macro mode can adapt to different versions of the wrapped Java library automatically,\njust by changing the dependency.\n- Macro mode doesn't require a separate step to generate any Clojure wrapper source files,\nas everything happens during the normal build process.\n- CLI mode is easier to debug, as the generated source is available to inspect.\n- CLI mode tends to generate better error messages when something goes wrong, since they can refer to lines in the generated code as opposed to just the macro call.\n- CLI mode can automatically add a `:refer-clojure :exclude` clause to the `ns` form to avoid compiler warrnings about redefined symbols.\n- others...?\n\n### Macro mode\n\n#### Installation\n\nAdd Tortilla as a dependency to your project.\nThe latest version is:\n\n[![Clojars Project](https://clojars.org/emlyn/tortilla/latest-version.svg)](https://clojars.org/emlyn/tortilla)\n\n#### Defining wrappers\n\nYou define wrappers for a Java class by calling the `defwrapper` macro, for example:\n\n``` clojure\n;; define the wrapper functions:\n(defwrapper java.io.File)\n\n;; create a File object\n(def f (clojure.java.io/file \"project.clj\"))\n\n;; call the generated functions on the File object\n(get-name f)      ;; =\u003e \"project.clj\"\n(is-directory f)  ;; =\u003e false\n(exists f)        ;; =\u003e true\n(length f)        ;; =\u003e 2996\n(last-modified f) ;; =\u003e 1602755163000\n```\n\nIn the above example, Clojure will emit a warning like:\n\n``` text\nWARNING: list already refers to: #'clojure.core/list in namespace: user, being replaced by: #'user/list\n```\n\nThis is because the File class has a member function called `list`, so when the wrapper function is created, this clashes with the `list` function in Clojure core.\nThere are two ways to work around this:\n\nEither you can exclude the Clojure core functions from being imported by adding a clause to your `ns` form like:\n\n``` clojure\n(:refer-clojure :exclude [list])\n```\n\nIf you use the CLI mode (see below), this can be handled automatically. If you still need to refer to the excluded function, you can still access it using its fully-qualified name (`clojure.core/list`).\n\nAlternatively, you can add a prefix to all generated function names when generating the wrappers:\n\n``` clojure\n(defwrapper java.io.File {:prefix \"f-\"})\n\n(f-get-name f) ;; =\u003e \"project.clj\"\n```\n\nNote that it's still possible to get clashes with a prefix, for example if a method is called `cat` and you use a prefix of `lazy-`, the generated function will clash with Clojure's `lazy-cat`.\n\n#### Filtering\n\n#### Coercion\n\nTortilla can handle the automatic coercion of values to Java types.\nBy default it will coerce:\n- Long values to Integers, so Java functions that expect Integers can be called with `(f 1)` instead of manually coercing like `(f (int 1))`.\n- Double values to Floats, so you can use `(f 1.0)` instead of `(f (float 1.0))`.\n- Keywords to Enums, so instead of having to include `(:import [package.name EnumClass])` in your ns declaration then use `(f EnumClass/VALUE)`, you can just use `(f :VALUE)`.\n- Vectors to Java arrays, so if a function expects an array of Strings, you can just use `(f [\"Hello\" \"world\"])` instead of `(f (into-arrray String [\"Hello\" \"world\"]))`.\n- Functions into Java Functional Interfaces (types that accept lambda expressions in Java 8+), so you can call, for example java.io.File::list, as `(list file #(str/ends-with? %2 \".txt\"))` instead of having to do something like:\n  ```clojure\n  (let [filter (reify java.io.FilenameFilter\n                 (accept [_self _dir name]\n                   (str/ends-with? name \".txt\")))]\n    (list file filter))\n  ```\n  Note that tortilla currently by default only supports a predetermined list of Functional Interfaces (listed in `tortilla.coerce`).\n  Support for other types can be added by calling the `tortilla.coerce/coerce-fn-impl` macro with the new Functional Interface.\n\nThe default coerce implementation can be extended by extending the `Coercible` protocol in `tortilla.coerce` to new Clojure types and/or defining new methods for the `coerce-long`, `coerce-double`, `coerce-kw`, `coerce-vector` and `coerce-fn` multimethods.\n\nAlternatively, a completely different implementation can be passsed in to `defwrapper` (or the keyword `:none` to disable coercion). The new function should accept two arguments: the clojure value and a `Class` object representing the target type. It should return either a value of the target type if it can coerce successfully, or the original value unaltered.\n\n### CLI Mode\n\n#### Installing\n\nYou can download the latest CLI executable from [github](//github.com/emlyn/tortilla/releases),\nor build it from source with `lein bin` in a clone of this repository, which will put the executable in the `bin` subdirectory.\n\n#### Running\n\nYou can get an overview of the options available with the `--help`/`-h` option:\n\n```text\n\u003e ./bin/tortilla -h\nUsage: tortilla [options]\n\nOptions:\n  -c, --class CLASS               Class to generate a wrapper. May be specified multiple times.\n  -m, --members                   Print list of class members instead of wrapper code (useful for checking -i/-x).\n  -i, --include REGEX             Only wrap members that match REGEX. Match members in format name(arg1.type,arg2.type):return.type\n  -x, --exclude REGEX             Exclude members that match REGEX from wrapping.\n  -n, --namespace NAMESPACE       Generate ns form at start of output with given name.\n      --[no-]refer-clojure        Generate refer-clojure clause excluding any wrapped names.\n      --coerce SYMBOL             Use SYMBOL for coercion (or 'none' to disable, empty for default).\n  -p, --prefix PREFIX             Prefix generated function names (useful to avoid conflicts with clojure.core names.)\n  -o, --out FILE                  Write generated output to FILE.\n      --[no-]metadata             Include metadata in output.\n      --[no-]instrument           Instrument specs.\n      --[no-]unwrap-do            Unwrap 'do' form around defns.\n  -w, --width CHARS          100  Limit output width.\n  -d, --dep COORD                 Add jars to classpath. May be specified multiple times. COORD may be in leiningen format ('[group/artifact \"version\"]') or maven format (group:artifact:version). In both cases the group part is optional, and defaults to the artifact ID.\n  -v, --version                   Display version information.\n  -h, --help                      Display this help.\n```\n\nHere is an example of using the CLI to generate a wrapper file for the File class:\n\n``` text\n# Generate wrapper file:\n\u003e ./bin/tortilla --class java.io.File --namespace java.io.file --out src/java/io/file.clj\n\n# Look at the start of the generated file:\n\u003e head -n 10 src/java/io/file.clj\n(ns java.io.file\n  (:refer-clojure :exclude [list])\n  (:require [tortilla.wrap]))\n\n;; ==== java.io.File ====\n\n(clojure.core/defn can-execute\n  {:arglists '([java.io.File])}\n  (^{:tag java.lang.Boolean}\n   [p0_271]\n```\n\n## Inspiration\n\nTortilla is based on a\n[post](https://clojureverse.org/t/generating-reflection-free-java-wrappers/4421)\nby [Arne Brasseur](https://github.com/plexus)\non [ClojureVerse](https://clojureverse.org/).\nSince the code was only available in a\n[gist](https://gist.github.com/plexus/645f133fc4c154d1b7497c1b63efdf24),\nI took the liberty of cloning it into a full repo and developing it further.\n\n## License\n\nCopyright © 2019 Arne Brasseur and Emlyn Corrin\n\nThis program and the accompanying materials are made available under the\nterms of the Eclipse Public License 2.0 which is available at\nhttp://www.eclipse.org/legal/epl-2.0.\n\nThis Source Code may also be made available under the following Secondary\nLicenses when the conditions for such availability set forth in the Eclipse\nPublic License, v. 2.0 are satisfied: GNU General Public License as published by\nthe Free Software Foundation, either version 2 of the License, or (at your\noption) any later version, with the GNU Classpath Exception which is available\nat https://www.gnu.org/software/classpath/license.html.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femlyn%2Ftortilla","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Femlyn%2Ftortilla","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Femlyn%2Ftortilla/lists"}