forked from clojure/tools.namespace
-
Notifications
You must be signed in to change notification settings - Fork 0
/
parse.cljc
129 lines (113 loc) · 4.55 KB
/
parse.cljc
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
;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
;; distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this
;; distribution. By using this software in any fashion, you are
;; agreeing to be bound by the terms of this license. You must not
;; remove this notice, or any other, from this software.
(ns ^{:author "Stuart Sierra"
:doc "Parse Clojure namespace (ns) declarations and extract
dependencies."}
lambdaisland.tools.namespace.parse
(:require #?(:clj [clojure.tools.reader :as reader]
:cljs [cljs.tools.reader :as reader])
[clojure.set :as set]))
(defn comment?
"Returns true if form is a (comment ...)"
[form]
(and (list? form) (= 'comment (first form))))
(defn ns-decl?
"Returns true if form is a (ns ...) declaration."
[form]
(and (list? form) (= 'ns (first form))))
(def clj-read-opts
"Map of options for tools.reader/read allowing reader conditionals
with the :clj feature enabled."
{:read-cond :allow
:features #{:clj}})
(def cljs-read-opts
"Map of options for tools.reader/read allowing reader conditionals
with the :cljs feature enabled."
{:read-cond :allow
:features #{:cljs}})
(defn read-ns-decl
"Attempts to read a (ns ...) declaration from a reader, and returns
the unevaluated form. Returns the first top-level ns form found.
Returns nil if ns declaration cannot be found. Throws exception on
invalid syntax.
Note that read can execute code (controlled by
tools.reader/*read-eval*), and as such should be used only with
trusted sources. read-opts is passed through to tools.reader/read,
defaults to clj-read-opts"
([rdr]
(read-ns-decl rdr nil))
([rdr read-opts]
(let [opts (assoc (or read-opts clj-read-opts)
:eof ::eof)]
(loop []
(let [form (reader/read opts rdr)]
(cond
(ns-decl? form) form
(= ::eof form) nil
:else (recur)))))))
;;; Parsing dependencies
(defn- prefix-spec?
"Returns true if form represents a libspec prefix list like
(prefix name1 name1) or [com.example.prefix [name1 :as name1]]"
[form]
(and (sequential? form) ; should be a list, but often is not
(symbol? (first form))
(not-any? keyword? form)
(< 1 (count form)))) ; not a bare vector like [foo]
(defn- option-spec?
"Returns true if form represents a libspec vector containing optional
keyword arguments like [namespace :as alias] or
[namespace :refer (x y)] or just [namespace]"
[form]
(and (sequential? form) ; should be a vector, but often is not
(symbol? (first form))
(or (keyword? (second form)) ; vector like [foo :as f]
(= 1 (count form))))) ; bare vector like [foo]
(defn- deps-from-libspec [prefix form]
(cond (prefix-spec? form)
(mapcat (fn [f] (deps-from-libspec
(symbol (str (when prefix (str prefix "."))
(first form)))
f))
(rest form))
(option-spec? form)
(when-not (= :as-alias (second form))
(deps-from-libspec prefix (first form)))
(symbol? form)
(list (symbol (str (when prefix (str prefix ".")) form)))
(keyword? form) ; Some people write (:require ... :reload-all)
nil
:else
(throw (ex-info "Unparsable namespace form"
{:reason ::unparsable-ns-form
:form form}))))
(def ^:private ns-clause-head-names
"Set of symbol/keyword names which can appear as the head of a
clause in the ns form."
#{"use" "require"})
(def ^:private ns-clause-heads
"Set of all symbols and keywords which can appear at the head of a
dependency clause in the ns form."
(set (mapcat (fn [name] (list (keyword name)
(symbol name)))
ns-clause-head-names)))
(defn- deps-from-ns-form [form]
(when (and (sequential? form) ; should be list but sometimes is not
(contains? ns-clause-heads (first form)))
(mapcat #(deps-from-libspec nil %) (rest form))))
(defn name-from-ns-decl
"Given an (ns...) declaration form (unevaluated), returns the name
of the namespace as a symbol."
[decl]
(second decl))
(defn deps-from-ns-decl
"Given an (ns...) declaration form (unevaluated), returns a set of
symbols naming the dependencies of that namespace. Handles :use and
:require clauses but not :load."
[decl]
(set (mapcat deps-from-ns-form decl)))