-
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
neil new
: Add support for deps-based templates (#51)
- Loading branch information
Showing
5 changed files
with
485 additions
and
97 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,18 +17,15 @@ | |
(ns babashka.neil | ||
{:no-doc true}) | ||
|
||
(require '[babashka.classpath :as cp] | ||
'[babashka.cli :as cli] | ||
(require '[babashka.cli :as cli] | ||
'[babashka.curl :as curl] | ||
'[babashka.fs :as fs] | ||
'[borkdude.rewrite-edn :as r] | ||
'[cheshire.core :as cheshire] | ||
'[clojure.edn :as edn] | ||
'[clojure.set :as set] | ||
'[clojure.string :as str]) | ||
|
||
;; deps-new reads classpath from property | ||
(System/setProperty "java.class.path" (cp/get-classpath)) | ||
|
||
(def spec {:lib {:desc "Fully qualified library name."} | ||
:version {:desc "Optional. When not provided, picks newest version from Clojars or Maven Central."} | ||
:sha {:desc "When provided, assumes lib refers to Github repo."} | ||
|
@@ -42,15 +39,17 @@ | |
:deps-file {:ref "<file>" | ||
:desc "Add to <file> instead of deps.edn." | ||
:default "deps.edn"} | ||
:limit {:coerce :long} | ||
}) | ||
:limit {:coerce :long}}) | ||
|
||
|
||
(import java.net.URLEncoder) | ||
|
||
(def version "0.0.33") | ||
|
||
(def windows? (str/includes? (System/getProperty "os.name") "Windows")) | ||
|
||
(def bb? (System/getProperty "babashka.version")) | ||
|
||
(defn url-encode [s] (URLEncoder/encode s "UTF-8")) | ||
|
||
(def curl-opts | ||
|
@@ -113,11 +112,19 @@ | |
(namespace lib) (name lib) branch)) | ||
:sha))) | ||
|
||
(defn latest-github-tag [lib] | ||
(defn list-github-tags [lib] | ||
(let [lib (clean-github-lib lib)] | ||
(first | ||
(curl-get-json (format "https://api.github.com/repos/%s/%s/tags" | ||
(namespace lib) (name lib)))))) | ||
(curl-get-json (format "https://api.github.com/repos/%s/%s/tags" | ||
(namespace lib) (name lib))))) | ||
|
||
(defn latest-github-tag [lib] | ||
(-> (list-github-tags lib) | ||
first)) | ||
|
||
(defn find-github-tag [lib tag] | ||
(->> (list-github-tags lib) | ||
(filter #(= (:name %) tag)) | ||
first)) | ||
|
||
(def deps-template | ||
(str/triml " | ||
|
@@ -373,36 +380,195 @@ | |
:version (:version search-result) | ||
:description (pr-str (:description search-result)))))) | ||
|
||
(def deps-new-opts | ||
[; main opts | ||
:template | ||
:name | ||
:target-dir | ||
:overwrite | ||
|
||
; optional overrides | ||
:artifact/id | ||
:description | ||
:developer | ||
:group/id | ||
:main | ||
:name | ||
:now/date | ||
:now/year | ||
:raw-name | ||
:scm/domain | ||
:scm/user | ||
:scm/repo | ||
:top | ||
:user | ||
:version]) | ||
|
||
(defn run-deps-new [{:keys [opts]}] | ||
(let [{:keys [template] | ||
:or {template "scratch"}} opts | ||
template-fn (requiring-resolve (symbol "org.corfield.new" template))] | ||
(template-fn (dissoc opts :template)) | ||
nil)) | ||
(defn- built-in-template? | ||
"Returns true if the template name maps to a function in org.corfield.new." | ||
[template] | ||
(contains? (set (map name (keys (ns-publics 'org.corfield.new)))) template)) | ||
|
||
(defn- github-repo-http-url [lib] | ||
(str "https://github.com/" (clean-github-lib lib))) | ||
|
||
(def github-repo-ssh-regex #"^[email protected]:([^/]+)/([^\.]+)\.git$") | ||
(def github-repo-http-regex #"^https://github.com/([^/]+)/([^\.]+)(\.git)?$") | ||
|
||
(defn- parse-git-url [git-url] | ||
(let [[[_ gh-user repo-name]] (or (re-seq github-repo-ssh-regex git-url) | ||
(re-seq github-repo-http-regex git-url))] | ||
(if (and gh-user repo-name) | ||
{:gh-user gh-user :repo-name repo-name} | ||
(throw (ex-info "Failed to parse :git/url" {:git/url git-url}))))) | ||
|
||
(defn- git-url->lib-sym [git-url] | ||
(when-let [{:keys [gh-user repo-name]} (parse-git-url git-url)] | ||
(symbol (str "io.github." gh-user) repo-name))) | ||
|
||
(def lib-opts->template-deps-fn | ||
"A map to define valid CLI options for deps-new template deps. | ||
- Each key is a sequence of valid combinations of CLI opts. | ||
- Each value is a function which returns a tools.deps lib map." | ||
{[#{:local/root}] | ||
(fn [lib-sym lib-opts] | ||
{lib-sym (select-keys lib-opts [:local/root])}) | ||
|
||
[#{} #{:git/url}] | ||
(fn [lib-sym lib-opts] | ||
(let [url (or (:git/url lib-opts) (github-repo-http-url lib-sym)) | ||
{:keys [name commit]} (latest-github-tag (git-url->lib-sym url))] | ||
{lib-sym {:git/url url :git/tag name :git/sha (:sha commit)}})) | ||
|
||
[#{:git/tag} #{:git/url :git/tag}] | ||
(fn [lib-sym lib-opts] | ||
(let [url (or (:git/url lib-opts) (github-repo-http-url lib-sym)) | ||
tag (:git/tag lib-opts) | ||
{:keys [commit]} (find-github-tag (git-url->lib-sym url) tag)] | ||
{lib-sym {:git/url url :git/tag tag :git/sha (:sha commit)}})) | ||
|
||
[#{:git/sha} #{:git/url :git/sha}] | ||
(fn [lib-sym lib-opts] | ||
(let [url (or (:git/url lib-opts) (github-repo-http-url lib-sym)) | ||
sha (:git/sha lib-opts)] | ||
{lib-sym {:git/url url :git/sha sha}})) | ||
|
||
[#{:latest-sha} #{:git/url :latest-sha}] | ||
(fn [lib-sym lib-opts] | ||
(let [url (or (:git/url lib-opts) (github-repo-http-url lib-sym)) | ||
sha (latest-github-sha (git-url->lib-sym url))] | ||
{lib-sym {:git/url url :git/sha sha}})) | ||
|
||
[#{:git/url :git/tag :git/sha}] | ||
(fn [lib-sym lib-opts] | ||
{lib-sym (select-keys lib-opts [:git/url :git/tag :git/sha])})}) | ||
|
||
(def valid-lib-opts | ||
"The set of all valid combinations of deps-new template deps opts." | ||
(into #{} cat (keys lib-opts->template-deps-fn))) | ||
|
||
(defn- deps-new-cli-opts->lib-opts | ||
"Returns parsed deps-new template deps opts from raw CLI opts." | ||
[cli-opts] | ||
(-> cli-opts | ||
(set/rename-keys {:sha :git/sha}) | ||
(select-keys (into #{} cat valid-lib-opts)))) | ||
|
||
(defn- invalid-lib-opts-error [provided-lib-opts] | ||
(ex-info (str "Provided invalid combination of CLI options for deps-new " | ||
"template deps.") | ||
{:provided-opts (set (keys provided-lib-opts)) | ||
:valid-combinations valid-lib-opts})) | ||
|
||
(defn- find-template-deps-fn | ||
"Returns a template-deps-fn given lib-opts parsed from raw CLI opts." | ||
[lib-opts] | ||
(some (fn [[k v]] (and (contains? (set k) (set (keys lib-opts))) v)) | ||
lib-opts->template-deps-fn)) | ||
|
||
(defn- template-deps | ||
"Returns a tools.deps lib map for the given CLI opts." | ||
[template cli-opts] | ||
(let [lib-opts (deps-new-cli-opts->lib-opts cli-opts) | ||
lib-sym (edn/read-string template) | ||
template-deps-fn (find-template-deps-fn lib-opts)] | ||
(if-not template-deps-fn | ||
(throw (invalid-lib-opts-error lib-opts)) | ||
(template-deps-fn lib-sym lib-opts)))) | ||
|
||
(def create-opts-deny-list | ||
[:deps-file :dry-run :git/sha :git/url :latest-sha :local/root :sha]) | ||
|
||
(defn- deps-new-plan | ||
"Returns a plan for calling org.corfield.new/create. | ||
:template-deps - These deps will be added with babashka.deps/add-deps before | ||
calling the create function. | ||
:create-opts - This map contains the options that will be passed to the | ||
create function." | ||
[cli-opts] | ||
(let [create-opts (merge {:template "scratch"} | ||
(apply dissoc cli-opts create-opts-deny-list)) | ||
tpl-deps (when (and bb? (not (built-in-template? (:template create-opts)))) | ||
(template-deps (:template create-opts) cli-opts))] | ||
(merge (when tpl-deps {:template-deps tpl-deps}) | ||
{:create-opts create-opts}))) | ||
|
||
(defn- deps-new-create [create-opts] | ||
((requiring-resolve 'org.corfield.new/create) create-opts)) | ||
|
||
(defn print-new-help [] | ||
(println (str/trim " | ||
Usage: neil new [template] [name] [target-dir] [options] | ||
Runs the org.corfield.new/create function from deps-new. | ||
All of the deps-new options can be provided as CLI options: | ||
https://github.com/seancorfield/deps-new/blob/develop/doc/options.md | ||
Both built-in and external templates are supported. Built-in templates use | ||
unqualified names (e.g. scratch) whereas external templates use fully-qualified | ||
names (e.g. io.github.kit/kit-clj). | ||
If an external template is provided, the babashka.deps/add-deps function will be | ||
called automatically before running org.corfield.new/create. The deps for the | ||
template are inferred automatically from the template name. The following | ||
options can be used to control the add-deps behavior: | ||
--local/root | ||
Override the :deps map to use the provided :local/root path. | ||
--git/url | ||
Override the :git/url in the :deps map. If no URL is provided, a template | ||
name starting with io.github or com.github is expected and the URL will | ||
point to GitHub. | ||
--git/tag | ||
Override the :git/tag in the :deps map. If no SHA or tag is provided, the | ||
latest tag from the default branch of :git/url will be used. | ||
--sha | ||
--git/sha | ||
Override the :git/sha in the :deps map. If no SHA or tag is provided, the | ||
latest tag from the default branch of :git/url will be used. | ||
--latest-sha | ||
Override the :git/sha in the :deps map with the latest SHA from the | ||
default branch of :git/url."))) | ||
|
||
(defn- deps-new-set-classpath | ||
"Sets the java.class.path property. | ||
This is required by org.corfield.new/create. In Clojure it's set by default, | ||
but in Babashka it must be set explicitly." | ||
[] | ||
(let [classpath ((requiring-resolve 'babashka.classpath/get-classpath))] | ||
(System/setProperty "java.class.path" classpath))) | ||
|
||
(defn- deps-new-add-template-deps | ||
"Adds template deps at runtime." | ||
[template-deps] | ||
((requiring-resolve 'babashka.deps/add-deps) {:deps template-deps})) | ||
|
||
(defn run-deps-new | ||
"Runs org.corfield.new/create using the provided CLI options. | ||
To support the dry run feature, side-effects should be described in the plan | ||
provided by deps-new-plan. This function's job is to execute side-effects | ||
using the plan to provide repeatability." | ||
[{:keys [opts]}] | ||
(if (:help opts) | ||
(print-new-help) | ||
(do | ||
(require 'org.corfield.new) | ||
(let [plan (deps-new-plan opts) | ||
{:keys [template-deps create-opts]} plan] | ||
(if (:dry-run opts) | ||
(do (prn plan) nil) | ||
(do | ||
(when template-deps (deps-new-add-template-deps template-deps)) | ||
(when bb? (deps-new-set-classpath)) | ||
(deps-new-create create-opts) | ||
nil)))))) | ||
|
||
(defn print-help [_] | ||
(println (str/trim " | ||
|
@@ -426,14 +592,13 @@ dep | |
add: Adds --lib, a fully qualified symbol, to deps.edn :deps. | ||
Run neil add dep --help to see all options. | ||
new: Create a new project using deps-new | ||
Runs one of the template functions in org.corfield.new. | ||
See the deps-new docs for all available options: | ||
https://github.com/seancorfield/deps-new/blob/develop/doc/options.md | ||
new: | ||
Create a project using deps-new | ||
Run neil new --help to see all options. | ||
Examples: | ||
neil new scratch foo --overwrite | ||
neil new io.github.rads/neil-new-test-template foo2 --latest-sha | ||
license | ||
list Lists commonly-used licenses available to be added to project. Takes an optional search string to filter results. | ||
|
@@ -503,7 +668,7 @@ license | |
{:cmds ["license" "list"] :fn license-search :cmds-opts [:search-term]} | ||
{:cmds ["license" "search"] :fn license-search :cmds-opts [:search-term]} | ||
{:cmds ["license" "add"] :fn add-license :cmds-opts [:license]} | ||
{:cmds ["new"] :fn run-deps-new :cmds-opts deps-new-opts} | ||
{:cmds ["new"] :fn run-deps-new :cmds-opts [:template :name :target-dir]} | ||
{:cmds ["version"] :fn print-version} | ||
{:cmds ["help"] :fn print-help} | ||
{:cmds [] :fn (fn [{:keys [opts] :as m}] | ||
|
Oops, something went wrong.