|  | 
| 1 |  | -(ns java-javadocs.core | 
|  | 1 | +(ns clojure.java.doc.impl | 
| 2 | 2 |   (:require | 
| 3 | 3 |     [clojure.string :as str]) | 
| 4 | 4 |   (:import [com.vladsch.flexmark.html2md.converter FlexmarkHtmlConverter] | 
| 5 | 5 |            [org.jsoup Jsoup])) | 
| 6 | 6 | 
 | 
| 7 | 7 | (set! *warn-on-reflection* true) | 
| 8 | 8 | 
 | 
| 9 |  | -(defn javadoc-url [^String classname] | 
|  | 9 | +(defn- check-java-version [^String version-str] | 
|  | 10 | +  (let [version (Integer/parseInt version-str) | 
|  | 11 | +        min-version 17] | 
|  | 12 | +    (when (< version min-version) | 
|  | 13 | +      (throw (ex-info | 
|  | 14 | +               (str "Java " min-version " or higher is required. Current version: " version-str) | 
|  | 15 | +               {:current-version version-str | 
|  | 16 | +                :minimum-version min-version}))))) | 
|  | 17 | + | 
|  | 18 | +(defn- javadoc-url [^String classname] | 
| 10 | 19 |   (let [java-version (System/getProperty "java.specification.version") | 
|  | 20 | +        _ (check-java-version java-version) | 
| 11 | 21 |         classname (str/replace classname #"\$.*" "") | 
| 12 | 22 |         klass (Class/forName classname) | 
| 13 | 23 |         module-name (.getName (.getModule klass)) | 
| 14 | 24 |         url-path (.replace classname \. \/)] | 
| 15 | 25 |     (str "https://docs.oracle.com/en/java/javase/" java-version "/docs/api/" module-name "/" url-path ".html"))) | 
| 16 | 26 | 
 | 
| 17 |  | -(defn html-to-md [^String html] | 
|  | 27 | +(defn- html-to-md [^String html] | 
| 18 | 28 |   (.convert ^FlexmarkHtmlConverter (.build (FlexmarkHtmlConverter/builder)) html)) | 
| 19 | 29 | 
 | 
| 20 | 30 | (defn- resolve-class-name [class-part] | 
|  | 
| 105 | 115 |         (str "^[" (str/join " " clojure-types) "] " class-part separator method-name)) | 
| 106 | 116 |       (str class-part separator method-name)))) | 
| 107 | 117 | 
 | 
| 108 |  | -  (defn parse-javadoc | 
| 109 |  | -    "parse the javadoc HTML for a class or method into a data structure: | 
| 110 |  | -    {:classname 'java.lang.String' | 
| 111 |  | -     :class-description-html '...' | 
| 112 |  | -     :class-description-md '...' | 
| 113 |  | -     :methods [...] | 
| 114 |  | -     :selected-method [{:signature 'valueOf(char[] data)' | 
| 115 |  | -                        :description 'Returns the string representation...' | 
| 116 |  | -                        :static? true | 
| 117 |  | -                        :clojure-call '^[char/1] String/valueOf' | 
| 118 |  | -                        :method-description-html '...' | 
| 119 |  | -                        :method-description-md '...'}]}" | 
| 120 |  | -    [s param-tags] | 
| 121 |  | -    (let [[class-part method-part] (str/split s #"/\.?" 2) | 
| 122 |  | -          class-name (resolve-class-name class-part) | 
| 123 |  | -          doc (Jsoup/parse (slurp (javadoc-url class-name))) | 
| 124 |  | -          class-desc-section (.selectFirst doc "section.class-description") | 
| 125 |  | -          method-rows (.select doc "div.method-summary-table.col-second") | 
| 126 |  | -          all-methods (vec (for [^org.jsoup.nodes.Element method-div method-rows] | 
| 127 |  | -                             (let [desc-div ^org.jsoup.nodes.Element (.nextElementSibling method-div) | 
| 128 |  | -                                   signature (.text (.select method-div "code")) | 
| 129 |  | -                                   modifier-div ^org.jsoup.nodes.Element (.previousElementSibling method-div) | 
| 130 |  | -                                   modifier-html (when modifier-div (.html modifier-div)) | 
| 131 |  | -                                   is-static? (and modifier-html (str/includes? modifier-html "static"))] | 
| 132 |  | -                               {:signature signature | 
| 133 |  | -                                :description (.text (.select desc-div ".block")) | 
| 134 |  | -                                :static? is-static? | 
| 135 |  | -                                :clojure-call (clojure-call-syntax class-part signature is-static?)}))) | 
| 136 |  | -          class-html (.outerHtml class-desc-section) | 
| 137 |  | -          result {:classname class-name | 
| 138 |  | -                  :class-description-html class-html | 
| 139 |  | -                  :class-description-md (html-to-md class-html) | 
| 140 |  | -                  :methods all-methods}] | 
| 141 |  | -      (if method-part | 
| 142 |  | -        (let [filtered (filter-methods all-methods method-part param-tags)] | 
| 143 |  | -          (assoc result :selected-method | 
| 144 |  | -                 (mapv #(get-method-detail doc %) filtered))) | 
| 145 |  | -        result))) | 
| 146 |  | - | 
| 147 |  | -(defn javadoc-data-fn [s param-tags] | 
| 148 |  | -  (parse-javadoc s param-tags)) | 
| 149 |  | - | 
| 150 |  | -(defn- print-javadoc [{:keys [class-description-md selected-method]}] | 
|  | 118 | +(defn parse-javadoc | 
|  | 119 | +  "parse the javadoc HTML for a class or method into a data structure: | 
|  | 120 | +  {:classname 'java.lang.String' | 
|  | 121 | +   :class-description-html '...' | 
|  | 122 | +   :class-description-md '...' | 
|  | 123 | +   :methods [...] | 
|  | 124 | +   :selected-method [{:signature 'valueOf(char[] data)' | 
|  | 125 | +                      :description 'Returns the string representation...' | 
|  | 126 | +                      :static? true | 
|  | 127 | +                      :clojure-call '^[char/1] String/valueOf' | 
|  | 128 | +                      :method-description-html '...' | 
|  | 129 | +                      :method-description-md '...'}]}" | 
|  | 130 | +  [s param-tags] | 
|  | 131 | +  (let [[class-part method-part] (str/split s #"/\.?" 2) | 
|  | 132 | +        class-name (resolve-class-name class-part) | 
|  | 133 | +        doc (Jsoup/parse (slurp (javadoc-url class-name))) | 
|  | 134 | +        class-desc-section (.selectFirst doc "section.class-description") | 
|  | 135 | +        method-rows (.select doc "div.method-summary-table.col-second") | 
|  | 136 | +        all-methods (vec (for [^org.jsoup.nodes.Element method-div method-rows] | 
|  | 137 | +                           (let [desc-div ^org.jsoup.nodes.Element (.nextElementSibling method-div) | 
|  | 138 | +                                 signature (.text (.select method-div "code")) | 
|  | 139 | +                                 modifier-div ^org.jsoup.nodes.Element (.previousElementSibling method-div) | 
|  | 140 | +                                 modifier-html (when modifier-div (.html modifier-div)) | 
|  | 141 | +                                 is-static? (and modifier-html (str/includes? modifier-html "static"))] | 
|  | 142 | +                             {:signature signature | 
|  | 143 | +                              :description (.text (.select desc-div ".block")) | 
|  | 144 | +                              :static? is-static? | 
|  | 145 | +                              :clojure-call (clojure-call-syntax class-part signature is-static?)}))) | 
|  | 146 | +        class-html (.outerHtml class-desc-section) | 
|  | 147 | +        result {:classname class-name | 
|  | 148 | +                :class-description-html class-html | 
|  | 149 | +                :class-description-md (html-to-md class-html) | 
|  | 150 | +                :methods all-methods}] | 
|  | 151 | +    (if method-part | 
|  | 152 | +      (let [filtered (filter-methods all-methods method-part param-tags)] | 
|  | 153 | +        (assoc result :selected-method | 
|  | 154 | +               (mapv #(get-method-detail doc %) filtered))) | 
|  | 155 | +      result))) | 
|  | 156 | + | 
|  | 157 | +(defn print-javadoc [{:keys [class-description-md selected-method]}] | 
| 151 | 158 |   (if selected-method | 
| 152 | 159 |     (doseq [{:keys [method-description-md]} selected-method] | 
| 153 | 160 |       (println method-description-md)) | 
| 154 | 161 |     (println class-description-md))) | 
| 155 |  | - | 
| 156 |  | -(defn javadoc-fn [s param-tags] | 
| 157 |  | -  (print-javadoc (javadoc-data-fn s param-tags))) | 
| 158 |  | - | 
| 159 |  | -(defmacro javadoc-data | 
| 160 |  | -  "a map containg javadoc data for a class or method | 
| 161 |  | -   Examples: | 
| 162 |  | -     (javadoc-data String)                   ; Get class data | 
| 163 |  | -     (javadoc-data String/valueOf)           ; Get data for all valueOf overloads | 
| 164 |  | -     (javadoc-data ^[char/1] String/valueOf) ; Get data for specific overload" | 
| 165 |  | -  [class-or-method] | 
| 166 |  | -  `(javadoc-data-fn ~(str class-or-method) '~(:param-tags (meta class-or-method)))) | 
| 167 |  | - | 
| 168 |  | -(defmacro javadoc | 
| 169 |  | -  "print javadoc for a class or method | 
| 170 |  | -   Examples: | 
| 171 |  | -     (javadoc String)                    ; Print class description | 
| 172 |  | -     (javadoc String/valueOf)            ; Print all valueOf overloads | 
| 173 |  | -     (javadoc ^[char/1] String/valueOf)  ; Print specific overload" | 
| 174 |  | -  [class-or-method] | 
| 175 |  | -  `(javadoc-fn ~(str class-or-method) '~(:param-tags (meta class-or-method)))) | 
0 commit comments