-
Notifications
You must be signed in to change notification settings - Fork 69
/
ns_parser.clj
149 lines (129 loc) · 5.01 KB
/
ns_parser.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
(ns refactor-nrepl.ns.ns-parser
"Extracts a list of imports or libspecs from an ns form. A libspec
looks like this:
{:ns my-ns
:as my-alias
:refer [referred symbols here] ; or :all
:rename {:rename :spec}
:only [only these symbols]
;; rest are cljs specific
:refer-macros [referred macros here]
:require-macros true}"
(:require [clojure.java.io :as io]
[clojure.set :as set]
[refactor-nrepl.core :as core])
(:import java.io.File))
(defn- libspec-vector->map
[libspec]
(if (vector? libspec)
(let [[ns & specs] libspec]
(into {:ns ns} (->> specs (partition 2) (map vec))))
{:ns (symbol libspec)}))
(defn- expand-prefix-specs
"Eliminate prefix lists."
[libspecs]
(let [prepend-prefix (fn add-prefix [prefix libspec]
(if (sequential? libspec)
(apply vector
(symbol (str prefix "." (first libspec)))
(rest libspec))
(symbol (str prefix "." libspec))))
normalize-libspec-vector (fn [libspec]
(if (core/prefix-form? libspec)
(let [prefix (first libspec)]
(map (partial prepend-prefix prefix)
(rest libspec)))
[libspec]))]
(mapcat normalize-libspec-vector libspecs)))
(defn- use-to-refer-all [use-spec]
(if (vector? use-spec)
(if (core/prefix-form? use-spec)
[(first use-spec) (map #(conj % :refer :all))]
(conj use-spec :refer :all))
[use-spec :refer :all]))
(defmacro with-libspecs-from
"Bind the symbol libspecs to the libspecs extracted from the key in
ns-form and execute body."
[ns-form key & body]
`(let [~'libspecs (rest (core/get-ns-component ~ns-form ~key))]
~@body))
(defn- extract-libspecs [ns-form]
(mapcat identity
[(with-libspecs-from ns-form :require
(->> libspecs
expand-prefix-specs
(map libspec-vector->map)))
(with-libspecs-from ns-form :use
(->> libspecs
expand-prefix-specs
(map use-to-refer-all)
(map libspec-vector->map)))]))
(defn get-libspecs [ns-form]
(some->> ns-form
extract-libspecs
distinct))
(defn get-imports [ns-form]
(let [expand-prefix-specs (fn [import-spec]
(if (sequential? import-spec)
(let [package (first import-spec)]
(map (fn [class-name]
(symbol (str package "." class-name)))
(rest import-spec)))
import-spec))]
(some->> (core/get-ns-component ns-form :import)
rest ; drop :import
(map expand-prefix-specs)
flatten
distinct)))
(defn get-required-macros [ns-form]
(into (with-libspecs-from ns-form :require-macros
(->> libspecs
(map libspec-vector->map)))
(with-libspecs-from ns-form :use-macros
(->> libspecs
(map libspec-vector->map)
(map #(set/rename-keys % {:only :refer}))))))
(defn- parse-clj-or-cljs-ns
([path] (parse-clj-or-cljs-ns path nil))
([path dialect]
(let [dialect (or dialect (core/file->dialect path))
ns-form (core/read-ns-form-with-meta dialect path)]
{dialect (merge {:require (get-libspecs ns-form)
:import (get-imports ns-form)}
(when (= dialect :cljs)
{:require-macros (get-required-macros ns-form)}))})))
(defn- parse-cljc-ns [path]
(merge (parse-clj-or-cljs-ns path :clj)
(parse-clj-or-cljs-ns path :cljs)))
(defn parse-ns [path-or-file]
(assoc
(if (core/cljc-file? (io/file path-or-file))
(parse-cljc-ns path-or-file)
(parse-clj-or-cljs-ns path-or-file))
:ns (second (core/read-ns-form-with-meta path-or-file))
:source-dialect (core/file->dialect path-or-file)))
(defn get-libspecs-from-file
"Return all the libspecs in a file.
This is the concatenation of all the libspecs found in the use,
use-macros, require and require-macros forms.
Note that no post-processing is done so there might be duplicates or
libspecs which could have been combined or eliminated as unused.
Dialect is either :clj or :cljs, the default is :clj."
([^File f]
(get-libspecs-from-file :clj f))
([dialect ^File f]
(some->> f
.getAbsolutePath
(core/read-ns-form-with-meta dialect)
((juxt get-libspecs get-required-macros))
(mapcat identity))))
(defn aliases
"Return a map of namespace aliases given a seq of libspecs.
e.g {str clojure.string}"
[libspecs]
(->> libspecs
(map (fn alias [{:keys [ns as]}]
(when as
{as ns})))
(remove nil?)
(apply merge)))