├── .gitignore ├── .hgignore ├── LICENSE ├── LICENSE.epl10 ├── README.md ├── build.boot ├── resources └── codeina │ └── css │ └── default.css └── src ├── codeina ├── core.clj ├── format.clj ├── reader │ ├── clojure.clj │ └── clojurescript.clj ├── utils.clj └── writer │ └── html.clj └── funcool └── boot_codeina.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | *.swp 9 | /.lein-* 10 | /.nrepl-port 11 | /doc/index.html 12 | /doc/api 13 | \#*\# 14 | *~ 15 | .\#* 16 | /.nrepl-history -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | target/** 3 | classes/** 4 | checkouts/** 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | .gitignore 12 | .git/** 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Andrey Antukh 2 | Copyright (c) 2014 James Reeves. 3 | 4 | The use and distribution terms for this software are covered by the 5 | Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 6 | which can be found in the file LICENSE at the root of this distribution. 7 | By using this software in any fashion, you are agreeing to be bound by 8 | the terms of this license. You must not remove this notice, or any other, 9 | from this software. -------------------------------------------------------------------------------- /LICENSE.epl10: -------------------------------------------------------------------------------- 1 | Eclipse Public License, Version 1.0 (EPL-1.0) 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and 10 | b) in the case of each subsequent Contributor: 11 | i) changes to the Program, and 12 | ii) additions to the Program; 13 | where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. 14 | "Contributor" means any person or entity that distributes the Program. 15 | 16 | "Licensed Patents " mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 17 | 18 | "Program" means the Contributions distributed in accordance with this Agreement. 19 | 20 | "Recipient" means anyone who receives the Program under this Agreement, including all Contributors. 21 | 22 | 2. GRANT OF RIGHTS 23 | 24 | a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. 25 | b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 26 | c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 27 | d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 28 | 3. REQUIREMENTS 29 | 30 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 31 | 32 | a) it complies with the terms and conditions of this Agreement; and 33 | b) its license agreement: 34 | i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 35 | ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 36 | iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and 37 | iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. 38 | When the Program is made available in source code form: 39 | 40 | a) it must be made available under this Agreement; and 41 | b) a copy of this Agreement must be included with each copy of the Program. 42 | Contributors may not remove or alter any copyright notices contained within the Program. 43 | Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. 44 | 45 | 4. COMMERCIAL DISTRIBUTION 46 | 47 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 48 | 49 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 50 | 51 | 5. NO WARRANTY 52 | 53 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 54 | 55 | 6. DISCLAIMER OF LIABILITY 56 | 57 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 58 | 59 | 7. GENERAL 60 | 61 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 62 | 63 | If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 64 | 65 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 66 | 67 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. 68 | 69 | This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # boot-codeina 2 | 3 | ```clojure 4 | [funcool/boot-codeina "0.1.0-SNAPSHOT"] 5 | ``` 6 | 7 | Tasks for generate beautiful api reference documentation for the [boot Clojure build tool][1] 8 | 9 | 10 | ## Usage 11 | 12 | Add `boot-codeina` to your `build.boot` dependencies and `require` the namespace: 13 | 14 | ```clj 15 | (set-env! :dependencies '[[funcool/boot-codeina "0.1.0-SNAPSHOT" :scope "test"]]) 16 | (require '[funcool.boot-codeina :refer :all]) 17 | 18 | (task-options! 19 | apidoc {:version "0.1.0" 20 | :title "MyPackage name" 21 | :sources #{"src"} 22 | :description "MyPackage description"}) 23 | ``` 24 | 25 | And now, execute the `apidoc` task: 26 | 27 | ```bash 28 | $ boot apidoc 29 | Generated HTML docs in /home/user/yourproject/doc/api 30 | ``` 31 | 32 | You can get the detailed information passing `-h` parameter to the `apidoc` task: 33 | 34 | ```bash 35 | $ boot apidoc -h 36 | Generate beautiful api documentation. 37 | 38 | Options: 39 | -h, --help Print this help info. 40 | -t, --title TITLE Set the project title to TITLE. 41 | -s, --sources SOURCES Conj SOURCES onto sources to read. 42 | -d, --description DESC Set the project description to DESC. 43 | -v, --version VERSION Set the project version to VERSION. 44 | -i, --include INCLUDE Conj INCLUDE onto include concrete namespaces. 45 | -x, --exclude EXCLUDE Conj EXCLUDE onto exclude concrete namespaces. 46 | -f, --format FORMAT Set docstring format to FORMAT. 47 | -o, --target OUTDIR Set the output directory to OUTDIR. 48 | -n, --root ROOTDIR Set the project root directory to ROOTDIR. 49 | -u, --src-uri SRCURI Set source code uri to SRCURI. 50 | -w, --writer WRITER Set documentation writer to WRITER. 51 | -r, --reader READER Set source reader to READER. 52 | ``` 53 | 54 | [1]: https://github.com/boot-clj/boot 55 | 56 | 57 | ## Examples ## 58 | 59 | - https://funcool.github.io/cats/latest/api/ 60 | - https://funcool.github.io/buddy-auth/latest/api/ 61 | - https://funcool.github.io/catacumba/latest/api/ 62 | -------------------------------------------------------------------------------- /build.boot: -------------------------------------------------------------------------------- 1 | (set-env! 2 | :resource-paths #{"src" "resources"} 3 | :dependencies '[[boot/core "2.0.0-rc14" :scope "provided"] 4 | [org.clojure/clojure "1.7.0-beta3" :scope "provided"] 5 | [org.clojure/clojurescript "0.0-3269"] 6 | [org.clojure/tools.namespace "0.2.10"] 7 | [org.pegdown/pegdown "1.4.2"] 8 | [leinjacker "0.4.2"] 9 | [hiccup "1.0.5"] 10 | [funcool/bootutils "0.1.0" :scope "test"]]) 11 | 12 | (require '[funcool.boot-codeina :refer :all] 13 | '[funcool.bootutils :refer :all]) 14 | 15 | (def +version+ 16 | "0.1.0-SNAPSHOT") 17 | 18 | (def +description+ 19 | "A tool for generating API documentation from Clojure") 20 | 21 | (task-options! 22 | pom {:project 'funcool/boot-codeina 23 | :version +version+ 24 | :description +description+ 25 | :url "https://github.com/funcool/codeina" 26 | :license {"Eclipse Public License" "http://www.eclipse.org/legal/epl-v10.html"} 27 | :scm {:url "https://github.com/funcool/boot-codeina"}} 28 | apidoc {:version +version+ 29 | :title "Boot-Codeina" 30 | :sources #{"src"} 31 | :src-uri "https://github.com/funcool/boot-codeina/tree/master/" 32 | :src-uri-prefix "#L" 33 | :description +description+}) 34 | -------------------------------------------------------------------------------- /resources/codeina/css/default.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Droid+Sans+Mono:200,300,400); 2 | @import url(http://fonts.googleapis.com/css?family=Lato:light,regular); 3 | @import url(http://fonts.googleapis.com/css?family=Ubuntu:300,400,500); 4 | 5 | body { 6 | font-family: "Lato", Helvetica, Arial, sans-serif; 7 | font-weight: 300; 8 | color:#585858; 9 | font-size: 100%; 10 | margin: 0px; 11 | } 12 | 13 | pre, code { 14 | font-family: "Droid Sans Mono","DejaVu Sans Mono","Monospace",monospace; 15 | font-weight: 300; 16 | } 17 | 18 | section.container { 19 | display: flex; 20 | font-size: 100%; 21 | } 22 | 23 | h2 { 24 | font-weight: normal; 25 | font-size: 3em; 26 | padding: 10px 0 2px 0; 27 | margin: 0; 28 | } 29 | 30 | header { 31 | color: #333; 32 | padding: 10px; 33 | } 34 | 35 | header small { 36 | font-style: italic; 37 | } 38 | 39 | header h1 { 40 | margin: 0; 41 | padding: 0; 42 | /* font-size: 12pt; */ 43 | font-weight: lighter; 44 | /* text-shadow: -1px -1px 0px #333; */ 45 | } 46 | 47 | header h1 a { 48 | color: #333; 49 | /* font-size: 32px; */ 50 | font-weight: 400; 51 | text-decoration: none; 52 | } 53 | 54 | #content { 55 | overflow: auto; 56 | background: #fff; 57 | color: #333; 58 | padding: 0 18px; 59 | font-size: 1.3em; 60 | } 61 | 62 | #namespaces { 63 | border-right: solid 1px #cccccc; 64 | min-width: 200px; 65 | padding-right: 15px; 66 | } 67 | 68 | #vars { 69 | border-right: solid 1px #cccccc; 70 | width: 200px; 71 | } 72 | 73 | .sidebar { 74 | overflow: auto; 75 | } 76 | 77 | .sidebar a { 78 | color: #333; 79 | display: block; 80 | text-decoration: none; 81 | } 82 | 83 | .sidebar h3 { 84 | margin: 0; 85 | padding: 10px 10px 0 10px; 86 | font-size: 19px; 87 | font-weight: normal; 88 | } 89 | 90 | .sidebar ul { 91 | padding: 0.5em 0em; 92 | margin: 0; 93 | } 94 | 95 | .sidebar li { 96 | display: block; 97 | vertical-align: middle; 98 | } 99 | 100 | .sidebar li a, .sidebar li .no-link { 101 | border-left: 3px solid transparent; 102 | padding: 0 15px; 103 | white-space: nowrap; 104 | } 105 | 106 | .sidebar li .no-link { 107 | display: block; 108 | color: #777; 109 | font-style: italic; 110 | } 111 | 112 | .sidebar li .inner { 113 | display: inline-block; 114 | padding-top: 7px; 115 | height: 24px; 116 | } 117 | 118 | .sidebar li a, .sidebar li .tree { 119 | height: 31px; 120 | /* height: 25px; */ 121 | } 122 | 123 | .depth-1 .inner { padding-left: 2px; } 124 | .depth-2 .inner { padding-left: 6px; } 125 | .depth-3 .inner { padding-left: 20px; } 126 | .depth-4 .inner { padding-left: 34px; } 127 | .depth-5 .inner { padding-left: 48px; } 128 | .depth-6 .inner { padding-left: 62px; } 129 | 130 | .sidebar li .tree { 131 | display: block; 132 | float: left; 133 | position: relative; 134 | top: -10px; 135 | margin: 0 4px 0 0; 136 | padding: 0; 137 | } 138 | 139 | .sidebar li.depth-1 .tree { 140 | display: none; 141 | } 142 | 143 | .sidebar li .tree .top, .sidebar li .tree .bottom { 144 | display: block; 145 | margin: 0; 146 | padding: 0; 147 | width: 7px; 148 | } 149 | 150 | .sidebar li .tree .top { 151 | border-left: 1px solid #aaa; 152 | border-bottom: 1px solid #aaa; 153 | height: 19px; 154 | } 155 | 156 | .sidebar li .tree .bottom { 157 | height: 22px; 158 | } 159 | 160 | .sidebar li.branch .tree .bottom { 161 | border-left: 1px solid #aaa; 162 | } 163 | 164 | #namespaces li.current a { 165 | border-left: 3px solid #a33; 166 | border-left: 3px solid #7a2518; 167 | color: #a33; 168 | color: #7a2518; 169 | 170 | } 171 | 172 | #vars li.current a { 173 | border-left: 3px solid #33a; 174 | color: #33a; 175 | } 176 | 177 | .namespace-docs h2 { 178 | color: #7a2518; 179 | } 180 | 181 | .namespace-docs h3 a { 182 | color: #ba3925; 183 | font-family: "Droid Sans Mono","DejaVu Sans Mono","Monospace",monospace; 184 | font-weight: 400; 185 | text-decoration: none; 186 | } 187 | 188 | .namespace-docs .usage code { 189 | display: block; 190 | color: #777; 191 | margin: 2px 0; 192 | font-size: 0.6em; 193 | } 194 | 195 | /* .usage code:first-child { */ 196 | /* padding-top: 10px; */ 197 | /* } */ 198 | 199 | 200 | 201 | .namespace-index h3 a { 202 | text-decoration: none; 203 | color: #ba3925; 204 | font-family: "Droid Sans Mono","DejaVu Sans Mono","Monospace",monospace; 205 | font-weight: 300; 206 | } 207 | 208 | .public h3 { 209 | margin: 0; 210 | } 211 | 212 | .public { 213 | margin: 0; 214 | border-top: 1px solid #efefef; 215 | padding-top: 14px; 216 | padding-bottom: 6px; 217 | } 218 | 219 | .public:last-child { 220 | margin-bottom: 20%; 221 | } 222 | 223 | .members .public:last-child { 224 | margin-bottom: 0; 225 | } 226 | 227 | .members { 228 | margin: 15px 0; 229 | } 230 | 231 | .members h4 { 232 | color: #555; 233 | font-weight: normal; 234 | font-variant: small-caps; 235 | margin: 0 0 5px 0; 236 | } 237 | 238 | .members .inner { 239 | padding-top: 5px; 240 | padding-left: 12px; 241 | margin-top: 2px; 242 | margin-left: 7px; 243 | border-left: 1px solid #bbb; 244 | } 245 | 246 | #content .members .inner h3 { 247 | /* font-size: 12pt; */ 248 | } 249 | 250 | .members .public { 251 | border-top: none; 252 | margin-top: 0; 253 | padding-top: 6px; 254 | padding-bottom: 0; 255 | } 256 | 257 | .members .public:first-child { 258 | padding-top: 0; 259 | } 260 | 261 | h4.type, 262 | h4.dynamic, 263 | h4.added, 264 | h4.deprecated { 265 | margin: 3px 10px 10spx 0; 266 | font-weight: bold; 267 | font-variant: small-caps; 268 | } 269 | 270 | .public h4.type, 271 | .public h4.dynamic, 272 | .public h4.added, 273 | .public h4.deprecated { 274 | font-weight: bold; 275 | /* margin: 3px 0 0 10px; */ 276 | font-size: 0.7em; 277 | } 278 | 279 | .members h4.type, 280 | .members h4.added, 281 | .members h4.deprecated { 282 | margin-top: 1px; 283 | } 284 | 285 | h4.type { 286 | color: #717171; 287 | } 288 | 289 | h4.dynamic { 290 | color: #9933aa; 291 | } 292 | 293 | h4.added { 294 | color: #508820; 295 | } 296 | 297 | h4.deprecated { 298 | color: #880000; 299 | } 300 | 301 | .namespace { 302 | margin-bottom: 40px; 303 | } 304 | 305 | .namespace:last-child { 306 | margin-bottom: 10%; 307 | } 308 | 309 | .index { 310 | padding: 0; 311 | margin: 15px 0; 312 | } 313 | 314 | .index * { 315 | display: inline; 316 | } 317 | 318 | .index p { 319 | padding-right: 3px; 320 | } 321 | 322 | .index li { 323 | padding-right: 5px; 324 | } 325 | 326 | .index li a { 327 | color: #333; 328 | font-family: "Droid Sans Mono","DejaVu Sans Mono","Monospace",monospace; 329 | font-size: 0.8em; 330 | text-decoration: none; 331 | font-weight: 300; 332 | } 333 | 334 | .index ul { 335 | padding-left: 0; 336 | } 337 | 338 | p { 339 | margin: 15px 0; 340 | } 341 | 342 | .public p:first-child, .public pre.plaintext { 343 | margin-top: 12px; 344 | } 345 | 346 | .doc { 347 | margin: 0 0 26px 0; 348 | clear: both; 349 | } 350 | 351 | .public .doc { 352 | margin: 0; 353 | } 354 | 355 | .namespace-index .doc { 356 | margin-bottom: 20px; 357 | } 358 | 359 | .namespace-index .namespace .doc { 360 | margin-bottom: 10px; 361 | } 362 | 363 | .markdown { 364 | /* line-height: 18px; */ 365 | /* font-size: 16px; */ 366 | } 367 | 368 | .doc, .public, .namespace .index { 369 | max-width: 780px; 370 | overflow-x: visible; 371 | } 372 | 373 | .markdown code, .src-link a { 374 | border-radius: 2px; 375 | font-size: 0.8em; 376 | color: #444; 377 | } 378 | 379 | .markdown pre { 380 | background: #f4f4f4; 381 | border: 1px solid #e0e0e0; 382 | /* border-radius: 2px; */ 383 | padding: 5px 5px; 384 | border-top: 1px solid #e0e0e0; 385 | border-bottom: 1px solid #e0e0e0; 386 | } 387 | 388 | .markdown pre code { 389 | background: transparent; 390 | border: none; 391 | } 392 | 393 | .doc ul, .doc ol { 394 | padding-left: 30px; 395 | } 396 | 397 | .doc table { 398 | border-collapse: collapse; 399 | margin: 0 10px; 400 | } 401 | 402 | .doc table td, .doc table th { 403 | border: 1px solid #dddddd; 404 | padding: 4px 6px; 405 | } 406 | 407 | .doc table th { 408 | background: #f2f2f2; 409 | } 410 | 411 | .doc dl { 412 | margin: 0 10px 20px 10px; 413 | } 414 | 415 | .doc dl dt { 416 | font-weight: bold; 417 | margin: 0; 418 | padding: 3px 0; 419 | border-bottom: 1px solid #ddd; 420 | } 421 | 422 | .doc dl dd { 423 | padding: 5px 0; 424 | margin: 0 0 5px 10px; 425 | } 426 | 427 | .doc abbr { 428 | border-bottom: 1px dotted #333; 429 | font-variant: none 430 | cursor: help; 431 | } 432 | 433 | .src-link { 434 | margin-bottom: 15px; 435 | } 436 | 437 | .src-link a { 438 | /* font-size: 70%; */ 439 | padding: 1px 4px; 440 | text-decoration: none; 441 | color: #5555bb; 442 | } -------------------------------------------------------------------------------- /src/codeina/core.clj: -------------------------------------------------------------------------------- 1 | (ns codeina.core) 2 | 3 | (defn- resolve-sym 4 | "Given a namespace qualified symbol, try resolve 5 | it and return the underlying value." 6 | [s] 7 | (let [ns-part (symbol (namespace s))] 8 | (try 9 | (require ns-part) 10 | (catch Exception e 11 | (throw (Exception. (str "Could not load codeina writer " s) e)))) 12 | (if-let [value (resolve s)] 13 | value 14 | (throw (Exception. (str "Could not resolve codeina writer " s)))))) 15 | 16 | (defmulti get-writer 17 | "Get writer function." 18 | :writer) 19 | (defmulti get-reader 20 | "Get reader function." 21 | :reader) 22 | 23 | (defmethod get-writer :html5 24 | [options] 25 | (resolve-sym 'codeina.writer.html/write-docs)) 26 | 27 | (defmethod get-reader :clojure 28 | [options] 29 | (resolve-sym 'codeina.reader.clojure/read-namespaces)) 30 | -------------------------------------------------------------------------------- /src/codeina/format.clj: -------------------------------------------------------------------------------- 1 | (ns codeina.format 2 | "Documentation writer that outputs HTML." 3 | (:import org.pegdown.PegDownProcessor 4 | org.pegdown.Extensions 5 | org.pegdown.LinkRenderer 6 | org.pegdown.LinkRenderer$Rendering 7 | org.pegdown.ast.WikiLinkNode) 8 | (:require [clojure.java.io :as io] 9 | [clojure.string :as str] 10 | [codeina.utils :as util] 11 | [hiccup.core :refer :all] 12 | [hiccup.page :refer :all] 13 | [hiccup.element :refer :all])) 14 | 15 | (def ^:private url-regex 16 | #"((https?|ftp|file)://[-A-Za-z0-9+()&@#/%?=~_|!:,.;]+[-A-Za-z0-9+()&@#/%=~_|])") 17 | 18 | (defn- add-anchors 19 | [text] 20 | (when text 21 | (str/replace text url-regex "$1"))) 22 | 23 | (def ^:private 24 | pegdown 25 | (PegDownProcessor. 26 | (bit-or Extensions/AUTOLINKS 27 | Extensions/QUOTES 28 | Extensions/SMARTS 29 | Extensions/STRIKETHROUGH 30 | Extensions/TABLES 31 | Extensions/FENCED_CODE_BLOCKS 32 | Extensions/WIKILINKS 33 | Extensions/DEFINITIONS 34 | Extensions/ABBREVIATIONS) 35 | 2000)) 36 | 37 | (defn- find-wikilink 38 | [options ns text] 39 | (let [ns-strs (map (comp str :name) (:namespaces options))] 40 | (if (contains? (set ns-strs) text) 41 | (str text ".html") 42 | (when-let [var (util/search-vars (:namespaces options) text (:name ns))] 43 | (str (namespace var) ".html#" (util/var-id var)))))) 44 | 45 | (defn- link-renderer 46 | [options ns] 47 | (proxy [LinkRenderer] [] 48 | (render 49 | ([node] 50 | (if (instance? WikiLinkNode node) 51 | (let [text (.getText node)] 52 | (LinkRenderer$Rendering. (find-wikilink options ns text) text)) 53 | (proxy-super render node))) 54 | ([node text] 55 | (proxy-super render node text)) 56 | ([node url title text] 57 | (proxy-super render node url title text))))) 58 | 59 | (defmulti format-docstring 60 | "Format the docstring of a var or namespace into HTML." 61 | (fn [options ns var] 62 | (or (:doc/format var) 63 | (:format options))) 64 | :default :markdown) 65 | 66 | (defmethod format-docstring :plaintext 67 | [_ _ metadata] 68 | (html 69 | [:pre.plaintext (add-anchors (h (:doc metadata)))])) 70 | 71 | (defmethod format-docstring :markdown 72 | [options ns metadata] 73 | (html 74 | [:div.markdown 75 | (when-let [doc (:doc metadata)] 76 | (.markdownToHtml pegdown doc (link-renderer options ns)))])) 77 | -------------------------------------------------------------------------------- /src/codeina/reader/clojure.clj: -------------------------------------------------------------------------------- 1 | (ns codeina.reader.clojure 2 | "Read raw documentation information from Clojure source directory." 3 | (:import java.io.File) 4 | (:require [clojure.java.io :as io] 5 | [clojure.tools.namespace :as ns] 6 | [clojure.string :as str] 7 | [codeina.utils :refer (assoc-some update-some correct-indent)])) 8 | 9 | (defn- sorted-public-vars 10 | "Return a sorted public vars from namespace." 11 | [namespace] 12 | (->> (ns-publics namespace) 13 | (vals) 14 | (sort-by (comp :name meta)))) 15 | 16 | (defn- no-doc? 17 | [var] 18 | (let [{:keys [skip-wiki no-doc]} (meta var)] 19 | (or skip-wiki no-doc))) 20 | 21 | (defn- proxy? [var] 22 | (re-find #"proxy\$" (-> var meta :name str))) 23 | 24 | (defn- macro? [var] 25 | (:macro (meta var))) 26 | 27 | (defn- multimethod? [var] 28 | (instance? clojure.lang.MultiFn (var-get var))) 29 | 30 | (defn- protocol? [var] 31 | (let [value (var-get var)] 32 | (and (map? value) (:on-interface value)))) 33 | 34 | (defn- protocol-method? [vars var] 35 | (if-let [p (:protocol (meta var))] 36 | (some #{p} vars))) 37 | 38 | (defn- protocol-methods [protocol vars] 39 | (filter #(= protocol (:protocol (meta %))) vars)) 40 | 41 | (defn- var-type [var] 42 | (cond 43 | (macro? var) :macro 44 | (multimethod? var) :multimethod 45 | (protocol? var) :protocol 46 | :else :var)) 47 | 48 | (defn- read-var [vars var] 49 | (-> (meta var) 50 | (select-keys [:name :file :line :arglists :doc :dynamic 51 | :added :deprecated :doc/format]) 52 | (update-some :doc correct-indent) 53 | (assoc-some :type (var-type var) 54 | :members (seq (map (partial read-var vars) 55 | (protocol-methods var vars)))))) 56 | 57 | (defn- read-publics 58 | [namespace] 59 | (let [vars (sorted-public-vars namespace)] 60 | (->> vars 61 | (remove proxy?) 62 | (remove no-doc?) 63 | (remove (partial protocol-method? vars)) 64 | (map (partial read-var vars)) 65 | (sort-by (comp str/lower-case :name))))) 66 | 67 | (defn- read-ns [namespace] 68 | (try 69 | (require namespace) 70 | (-> (find-ns namespace) 71 | (meta) 72 | (assoc :name namespace) 73 | (assoc :publics (read-publics namespace)) 74 | (update-some :doc correct-indent) 75 | (list)) 76 | (catch Exception e 77 | (println 78 | (format "Could not generate clojure documentation for %s - root cause: %s %s" 79 | namespace 80 | (.getName (class e)) 81 | (.getMessage e)))))) 82 | 83 | (defn- find-namespaces 84 | [^File directory] 85 | (ns/find-namespaces-in-dir directory)) 86 | 87 | (defn read-namespaces 88 | "Read Clojure namespaces from a source directory (defaults to 89 | \"src\"), and return a list of maps suitable for documentation 90 | purposes. 91 | 92 | Any namespace with {:no-doc true} in its metadata will be skipped. 93 | 94 | The keys in the maps are: 95 | :name - the name of the namespace 96 | :doc - the doc-string on the namespace 97 | :author - the author of the namespace 98 | :publics 99 | :name - the name of a public function, macro, or value 100 | :file - the file the var was declared in 101 | :line - the line at which the var was declared 102 | :arglists - the arguments the function or macro takes 103 | :doc - the doc-string of the var 104 | :type - one of :macro, :protocol, :multimethod or :var 105 | :added - the library version the var was added in 106 | :deprecated - the library version the var was deprecated in" 107 | ([path] 108 | (->> (io/file path) 109 | (find-namespaces) 110 | (mapcat read-ns) 111 | (remove :no-doc))) 112 | ([path & paths] 113 | (mapcat read-namespaces (cons path paths)))) 114 | -------------------------------------------------------------------------------- /src/codeina/reader/clojurescript.clj: -------------------------------------------------------------------------------- 1 | ;; (ns codeina.reader.clojurescript 2 | ;; "Read raw documentation information from ClojureScript source directory." 3 | ;; (:use [codeina.utils :only [assoc-some update-some correct-indent]]) 4 | ;; (:require [clojure.java.io :as io] 5 | ;; [cljs.analyzer :as an] 6 | ;; [clojure.string :as str])) 7 | 8 | ;; (defn- cljs-file? [file] 9 | ;; (and (.isFile file) 10 | ;; (-> file .getName (.endsWith ".cljs")))) 11 | 12 | ;; (defn- strip-parent [parent] 13 | ;; (let [len (inc (count (.getPath parent)))] 14 | ;; (fn [child] 15 | ;; (let [child-name (.getPath child)] 16 | ;; (if (>= (count child-name) len) 17 | ;; (io/file (subs child-name len))))))) 18 | 19 | ;; (defn- find-files [file] 20 | ;; (if (.isDirectory file) 21 | ;; (->> (file-seq file) 22 | ;; (filter cljs-file?) 23 | ;; (keep (strip-parent file))))) 24 | 25 | ;; (defn- no-doc? [var] 26 | ;; (or (:skip-wiki var) (:no-doc var))) 27 | 28 | ;; (defn- protocol-methods [protocol vars] 29 | ;; (let [proto-name (name (:name protocol))] 30 | ;; (filter #(if-let [p (:protocol %)] (= proto-name (name p))) vars))) 31 | 32 | ;; (defn- var-type [opts] 33 | ;; (cond 34 | ;; (:macro opts) :macro 35 | ;; (:protocol-symbol opts) :protocol 36 | ;; :else :var)) 37 | 38 | ;; (defn- read-var [file vars var] 39 | ;; (-> var 40 | ;; (select-keys [:name :line :arglists :doc :dynamic :added :deprecated :doc/format]) 41 | ;; (update-some :doc correct-indent) 42 | ;; (update-some :arglists second) 43 | ;; (assoc-some :file (.getPath file) 44 | ;; :type (var-type var) 45 | ;; :members (map (partial read-var file vars) 46 | ;; (protocol-methods var vars))))) 47 | 48 | ;; (defn- namespace-vars [analysis namespace] 49 | ;; (->> (get-in analysis [::an/namespaces namespace :defs]) 50 | ;; (map (fn [[name opts]] (assoc opts :name name))))) 51 | 52 | ;; (defn- read-publics [analysis namespace file] 53 | ;; (let [vars (namespace-vars analysis namespace)] 54 | ;; (->> vars 55 | ;; (remove :protocol) 56 | ;; (remove :anonymous) 57 | ;; (remove no-doc?) 58 | ;; (map (partial read-var file vars)) 59 | ;; (sort-by (comp str/lower-case :name))))) 60 | 61 | ;; (defn- analyze-file [file] 62 | ;; (binding [an/*analyze-deps* false] 63 | ;; (an/analyze-file file))) 64 | 65 | ;; (defn- read-file [path file] 66 | ;; (try 67 | ;; (let [analysis (analyze-file (io/file path file))] 68 | ;; (apply merge 69 | ;; (for [namespace (keys (::an/namespaces analysis))] 70 | ;; {namespace 71 | ;; (-> (get-in analysis [::an/namespaces namespace]) 72 | ;; (assoc :name namespace) 73 | ;; (assoc :publics (read-publics analysis namespace file)) 74 | ;; (update-some :doc correct-indent))}))) 75 | ;; (catch Exception e 76 | ;; (println 77 | ;; (format "Could not generate clojurescript documentation for %s - root cause: %s %s" 78 | ;; file 79 | ;; (.getName (class e)) 80 | ;; (.getMessage e)))))) 81 | 82 | ;; (defn read-namespaces 83 | ;; "Read ClojureScript namespaces from a source directory (defaults to 84 | ;; \"src\"), and return a list of maps suitable for documentation 85 | ;; purposes. 86 | 87 | ;; The keys in the maps are: 88 | ;; :name - the name of the namespace 89 | ;; :doc - the doc-string on the namespace 90 | ;; :author - the author of the namespace 91 | ;; :publics 92 | ;; :name - the name of a public function, macro, or value 93 | ;; :file - the file the var was declared in 94 | ;; :line - the line at which the var was declared 95 | ;; :arglists - the arguments the function or macro takes 96 | ;; :doc - the doc-string of the var 97 | ;; :type - one of :macro, :protocol or :var 98 | ;; :added - the library version the var was added in 99 | ;; :deprecated - the library version the var was deprecated in" 100 | ;; ([] (read-namespaces "src")) 101 | ;; ([path] 102 | ;; (let [path (io/file path) 103 | ;; file-reader (partial read-file path)] 104 | ;; (->> (find-files path) 105 | ;; (map file-reader) 106 | ;; (apply merge) 107 | ;; (vals) 108 | ;; (sort-by :name)))) 109 | ;; ([path & paths] 110 | ;; (mapcat read-namespaces (cons path paths)))) 111 | -------------------------------------------------------------------------------- /src/codeina/utils.clj: -------------------------------------------------------------------------------- 1 | (ns codeina.utils 2 | "Miscellaneous utility functions." 3 | (:import java.net.URLEncoder) 4 | (:require [clojure.string :as str] 5 | [clojure.java.io :as io])) 6 | 7 | (defn var-id 8 | [var] 9 | (str "var-" (-> var name URLEncoder/encode (str/replace "%" ".")))) 10 | 11 | (defn assoc-some 12 | "Associates a key with a value in a map, if and only if the value is not nil." 13 | ([m k v] 14 | (if (nil? v) m (assoc m k v))) 15 | ([m k v & kvs] 16 | (reduce (fn [m [k v]] (assoc-some m k v)) 17 | (assoc-some m k v) 18 | (partition 2 kvs)))) 19 | 20 | (defn update-some 21 | "Updates a key in a map with a function, if and only if the return value from 22 | the function is not nil." 23 | [m k f & args] 24 | (assoc-some m k (apply f (m k) args))) 25 | 26 | (defn- find-minimum 27 | [coll] 28 | (when (seq coll) 29 | (apply min coll))) 30 | 31 | (defn- find-smallest-indent 32 | [text] 33 | (->> (str/split-lines text) 34 | (remove str/blank?) 35 | (map #(re-find #"^\s+" %)) 36 | (map count) 37 | (find-minimum))) 38 | 39 | (defn unindent 40 | "Unindent a block of text by a specific amount or the smallest common 41 | indentation size." 42 | ([text] 43 | (unindent text (find-smallest-indent text))) 44 | ([text indent-size] 45 | (let [re (re-pattern (str "^\\s{0," indent-size "}"))] 46 | (->> (str/split-lines text) 47 | (map #(str/replace % re "")) 48 | (str/join "\n"))))) 49 | 50 | (defn correct-indent 51 | [text] 52 | (when text 53 | (let [lines (str/split-lines text)] 54 | (->> (rest lines) 55 | (str/join "\n") 56 | (unindent) 57 | (str (first lines) "\n"))))) 58 | 59 | (defn symbol-set 60 | "Accepts a single item (or a collection of items), converts them to 61 | symbols and returns them in set form." 62 | [x] 63 | (->> (if (coll? x) x [x]) 64 | (filter identity) 65 | (map symbol) 66 | (into #{}))) 67 | 68 | (defn ns-filter 69 | "Accepts a sequence of namespaces (generated by 70 | `codeina.reader/read-namespaces`), a sequence of namespaces to keep 71 | and a sequence of namespaces to drop. The sequence is returned with 72 | all namespaces in `exclude` and all namespaces NOT in `include` 73 | removed." 74 | [include exclude ns-seq] 75 | (let [has-name? (fn [names] (comp (symbol-set names) :name)) 76 | ns-seq (remove (has-name? exclude) ns-seq)] 77 | (if include 78 | (filter (has-name? include) ns-seq) 79 | ns-seq))) 80 | 81 | (defn summary 82 | "Return the summary of a docstring. 83 | The summary is the first portion of the string, from the first 84 | character to the first page break (\f) character OR the first TWO 85 | newlines." 86 | [s] 87 | (if s 88 | (->> (str/trim s) 89 | (re-find #"(?s).*?(?=\f)|.*?(?=\n\n)|.*")))) 90 | 91 | (defn public-vars 92 | "Return a list of all public var names in a collection of namespaces from one 93 | of the reader functions." 94 | [namespaces] 95 | (for [ns namespaces 96 | var (:publics ns) 97 | v (concat [var] (:members var))] 98 | (symbol (str (:name ns)) (str (:name v))))) 99 | 100 | (defn- unix-path 101 | [path] 102 | (.replace path "\\" "/")) 103 | 104 | (defn- normalize-path 105 | [path root] 106 | (let [root (str (unix-path root) "/") 107 | path (unix-path (.getAbsolutePath (io/file path)))] 108 | (if (.startsWith path root) 109 | (.substring path (.length root)) 110 | path))) 111 | 112 | (defn- find-file-in-repo 113 | "Given a classpath-relative file (as from the output of 114 | `codeina.reader/read-namespaces`), and a sequence of source directory paths, 115 | returns a File object indicating the file from the repo root." 116 | [file sources] 117 | (if (and file (not (.isAbsolute (io/file file)))) 118 | (->> (map #(io/file % file) sources) 119 | (filter #(.exists %)) 120 | first))) 121 | 122 | (defn add-source-paths 123 | "Accepts a sequence of namespaces (generated by 124 | `codeina.reader/read-namespaces`), the project root, and a list of 125 | source directories. The sequence is returned with :path items added 126 | in each public var's entry in the :publics map, which indicate the 127 | path to the source file relative to the repo root." 128 | [root sources ns-seq] 129 | (let [sources (map #(normalize-path % root) sources)] 130 | (for [ns ns-seq] 131 | (assoc ns 132 | :publics (map #(assoc % :path (find-file-in-repo (:file %) sources)) 133 | (:publics ns)))))) 134 | 135 | (def ^:private re-chars (set "\\.*+|?()[]{}$^")) 136 | 137 | (defn re-escape 138 | "Escape a string so it can be safely placed in a regex." 139 | [s] 140 | (str/escape s #(if (re-chars %) (str \\ %)))) 141 | 142 | (defn search-vars 143 | "Find the best-matching var given a partial var string, a list of namespaces, 144 | and an optional starting namespace." 145 | [namespaces partial-var & [starting-ns]] 146 | (let [regex (if (.contains partial-var "/") 147 | (re-pattern (str (re-escape partial-var) "$")) 148 | (re-pattern (str "/" (re-escape partial-var) "$"))) 149 | matches (filter 150 | #(re-find regex (str %)) 151 | (public-vars namespaces))] 152 | (or (first (filter #(= (str starting-ns) (namespace %)) matches)) 153 | (first matches)))) 154 | -------------------------------------------------------------------------------- /src/codeina/writer/html.clj: -------------------------------------------------------------------------------- 1 | (ns codeina.writer.html 2 | "Documentation writer that outputs HTML." 3 | (:import java.io.File) 4 | (:require [clojure.java.io :as io] 5 | [clojure.string :as str] 6 | [codeina.utils :as util] 7 | [codeina.format :as fmt] 8 | [hiccup.core :refer :all] 9 | [hiccup.page :refer :all] 10 | [hiccup.element :refer :all])) 11 | 12 | (defn- ns-filename 13 | [namespace] 14 | (str (:name namespace) ".html")) 15 | 16 | (defn- ns-filepath 17 | [output-dir namespace] 18 | (str output-dir "/" (ns-filename namespace))) 19 | 20 | (defn- var-uri 21 | [namespace var] 22 | (str (ns-filename namespace) "#" (util/var-id (:name var)))) 23 | 24 | (defn- get-mapping-fn 25 | [mappings path] 26 | (some (fn [[re f]] (if (re-find re path) f)) mappings)) 27 | 28 | (defn- uri-path 29 | [path] 30 | (str/replace (str path) File/separator "/")) 31 | 32 | 33 | (defn- var-source-uri 34 | [{:keys [src-uri src-uri-mapping src-uri-prefix]} 35 | {:keys [path file line]}] 36 | (let [path (uri-path path) 37 | file (uri-path file)] 38 | (str src-uri 39 | (if-let [mapping-fn (get-mapping-fn src-uri-mapping path)] 40 | (mapping-fn file) 41 | path) 42 | (if src-uri-prefix 43 | (str src-uri-prefix line))))) 44 | 45 | (defn- split-ns 46 | [namespace] 47 | (str/split (str namespace) #"\.")) 48 | 49 | (defn- namespace-parts 50 | [namespace] 51 | (->> (split-ns namespace) 52 | (reductions #(str %1 "." %2)) 53 | (map symbol))) 54 | 55 | (defn- add-depths 56 | [namespaces] 57 | (->> namespaces 58 | (map (juxt identity (comp count split-ns))) 59 | (reductions (fn [[_ ds] [ns d]] [ns (cons d ds)]) [nil nil]) 60 | (rest))) 61 | 62 | (defn- add-heights 63 | [namespaces] 64 | (for [[ns ds] namespaces] 65 | (let [d (first ds) 66 | h (count (take-while #(not (or (= d %) (= (dec d) %))) (rest ds)))] 67 | [ns d h]))) 68 | 69 | (defn- add-branches 70 | [namespaces] 71 | (->> (partition-all 2 1 namespaces) 72 | (map (fn [[[ns d0 h] [_ d1 _]]] [ns d0 h (= d0 d1)])))) 73 | 74 | (defn- namespace-hierarchy [namespaces] 75 | (->> (map :name namespaces) 76 | (sort) 77 | (mapcat namespace-parts) 78 | (distinct) 79 | (add-depths) 80 | (add-heights) 81 | (add-branches))) 82 | 83 | (defn- index-by [f m] 84 | (into {} (map (juxt f identity) m))) 85 | 86 | ;; The values in ns-tree-part are chosen for aesthetic reasons, based 87 | ;; on a text size of 15px and a line height of 31px. 88 | 89 | (defn- ns-tree-part [height] 90 | (if (zero? height) 91 | [:span.tree [:span.top] [:span.bottom]] 92 | (let [row-height 31 93 | top (- 0 21 (* height row-height)) 94 | height (+ 0 30 (* height row-height))] 95 | [:span.tree {:style (str "top: " top "px;")} 96 | [:span.top {:style (str "height: " height "px;")}] 97 | [:span.bottom]]))) 98 | 99 | (defn- namespaces-menu [options & [current]] 100 | (let [namespaces (:namespaces options) 101 | ns-map (index-by :name namespaces)] 102 | [:div#namespaces.sidebar 103 | [:h3 (link-to "index.html" [:span.inner "Namespaces"])] 104 | [:ul 105 | (for [[name depth height branch?] (namespace-hierarchy namespaces)] 106 | (let [class (str "depth-" depth (if branch? " branch")) 107 | short (last (split-ns name)) 108 | inner [:div.inner (ns-tree-part height) [:span (h short)]]] 109 | (if-let [ns (ns-map name)] 110 | (let [class (str class (if (= ns current) " current"))] 111 | [:li {:class class} (link-to (ns-filename ns) inner)]) 112 | [:li {:class class} [:div.no-link inner]])))]])) 113 | 114 | (defn- sorted-public-vars 115 | [namespace] 116 | (sort-by (comp str/lower-case :name) (:publics namespace))) 117 | 118 | (def ^{:private true} 119 | default-includes 120 | (list 121 | [:meta {:charset "UTF-8"}] 122 | (include-css "css/default.css"))) 123 | 124 | (defn- project-title 125 | [options] 126 | (str (:title options) " " (:version options))) 127 | 128 | (defn- header 129 | [options] 130 | (let [title (format "%s Api Documentation" (:title options))] 131 | [:header 132 | [:section.title 133 | [:h1 (link-to "index.html" (h title))]] 134 | [:small "Version: " (:version options)]])) 135 | 136 | (defn- index-page 137 | [options] 138 | (html5 139 | [:head 140 | default-includes 141 | [:title (h (project-title options)) " API documentation"]] 142 | [:body 143 | (header options) 144 | [:section.container 145 | (namespaces-menu options) 146 | [:section#content.namespace-index 147 | [:section.title-container 148 | [:h2 (h (:title options))] 149 | [:div.doc [:p (h (:description options))]]] 150 | (for [namespace (sort-by :name (:namespaces options))] 151 | [:div.namespace 152 | [:h3 (link-to (ns-filename namespace) (h (:name namespace)))] 153 | [:div.doc (fmt/format-docstring options nil (update-in namespace [:doc] util/summary))] 154 | [:div.index 155 | [:p "Public variables and functions:"] 156 | (unordered-list 157 | (for [var (sorted-public-vars namespace)] 158 | (list " " (link-to (var-uri namespace var) (h (:name var))) " ")))]])]]])) 159 | 160 | (defn- var-usage [var] 161 | (for [arglist (:arglists var)] 162 | (list* (:name var) arglist))) 163 | 164 | (defn- added-and-deprecated-docs [var] 165 | (list 166 | (if-let [added (:added var)] 167 | [:h4.added "added in " added]) 168 | (if-let [deprecated (:deprecated var)] 169 | [:h4.deprecated "deprecated" (if (string? deprecated) (str " in " deprecated))]))) 170 | 171 | (defn- var-docs [options namespace var] 172 | [:div.public.anchor {:id (h (util/var-id (:name var)))} 173 | [:h3 174 | (link-to (str "#" (util/var-id (:name var))) 175 | (h (:name var)))] 176 | (if-not (= (:type var) :var) 177 | [:h4.type (name (:type var))]) 178 | (if (:dynamic var) 179 | [:h4.dynamic "dynamic"]) 180 | (added-and-deprecated-docs var) 181 | [:div.usage 182 | (for [form (var-usage var)] 183 | [:code (h (pr-str form))])] 184 | [:div.doc (fmt/format-docstring options namespace var)] 185 | (if-let [members (seq (:members var))] 186 | [:div.members 187 | [:h4 "members"] 188 | [:div.inner 189 | (let [options (dissoc options :src-uri)] 190 | (map (partial var-docs options namespace) members))]]) 191 | (if (:src-uri options) 192 | (if (:file var) 193 | [:div.src-link (link-to (var-source-uri options var) "view source")] 194 | (println "Could not generate source link for" (:name var))))]) 195 | 196 | (defn- namespace-page [options namespace] 197 | (html5 198 | [:head 199 | default-includes 200 | [:title (h (:name namespace)) " documentation"]] 201 | [:body 202 | (header options) 203 | [:section.container 204 | (namespaces-menu options namespace) 205 | [:section#content.namespace-docs 206 | [:h2#top.anchor (h (:name namespace))] 207 | (added-and-deprecated-docs namespace) 208 | [:div.doc (fmt/format-docstring options nil namespace)] 209 | (for [var (sorted-public-vars namespace)] 210 | (var-docs options namespace var))]]])) 211 | 212 | (defn- copy-resource! 213 | [^String output-dir src dest] 214 | (io/copy (io/input-stream (io/resource src)) 215 | (io/file output-dir dest))) 216 | 217 | (defn- mkdirs! 218 | [^String output-dir & dirs] 219 | (doseq [dir dirs] 220 | (.mkdirs (io/file output-dir dir)))) 221 | 222 | (defn- write-index! 223 | [^String output-dir options] 224 | (spit (io/file output-dir "index.html") (index-page options))) 225 | 226 | (defn- write-namespaces! 227 | [^String output-dir {:keys [namespaces] :as options}] 228 | (doseq [namespace namespaces] 229 | (spit (ns-filepath output-dir namespace) 230 | (namespace-page options namespace)))) 231 | 232 | (defn write-docs 233 | "Take raw documentation info and turn it into formatted HTML." 234 | [{:keys [target namespaces] :as options}] 235 | (mkdirs! target "css") 236 | (copy-resource! target "codeina/css/default.css" "css/default.css") 237 | (write-index! target options) 238 | (write-namespaces! target options) 239 | (println "Generated HTML docs in" 240 | (.getAbsolutePath (io/file target)))) 241 | -------------------------------------------------------------------------------- /src/funcool/boot_codeina.clj: -------------------------------------------------------------------------------- 1 | (ns funcool.boot-codeina 2 | {:boot/export-tasks true} 3 | (:require [codeina.utils :as utils] 4 | [codeina.core :as core] 5 | [boot.core :refer :all] 6 | [boot.task.built-in :refer :all])) 7 | 8 | (def ^:private 9 | +defaults+ {:target "doc/api" 10 | :format :markdown 11 | :root (System/getProperty "user.dir") 12 | :src-uri nil 13 | :src-uri-prefix nil 14 | :reader :clojure 15 | :writer :html5}) 16 | 17 | (deftask apidoc 18 | "Generate beautiful api documentation." 19 | [t title TITLE str "The project title." 20 | s sources SOURCES #{str} "Sources to read." 21 | d description DESC str "The project description." 22 | v version VERSION str "The project version." 23 | i include INCLUDE [sym] "Include concrete namespaces." 24 | x exclude EXCLUDE [sym] "Exclude concrete namespaces." 25 | f format FORMAT kw "Docstring format." 26 | o target OUTDIR str "The output directory." 27 | n root ROOTDIR src "The project root directory." 28 | u src-uri SRCURI str "Source code uri" 29 | w writer WRITER kw "Documentation writer." 30 | r reader READER kw "Source reader."] 31 | (fn [next-handler] 32 | (fn [fileset] 33 | (let [options (merge +defaults+ *opts*) 34 | reader-fn (core/get-reader options) 35 | writter-fn (core/get-writer options) 36 | root (:root options) 37 | namespaces (->> (apply reader-fn sources) 38 | (utils/ns-filter include exclude) 39 | (utils/add-source-paths root sources))] 40 | (writter-fn (assoc options :namespaces namespaces))) 41 | (next-handler fileset)))) 42 | --------------------------------------------------------------------------------