nitdoc: introduce IntroRedefListPhase
[nit.git] / src / doc / doc_pages.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Nitdoc page generation
16 module doc_pages
17
18 import toolcontext
19 import doc_model
20 private import json::static
21
22 redef class ToolContext
23 private var opt_dir = new OptionString("output directory", "-d", "--dir")
24 private var opt_source = new OptionString("link for source (%f for filename, %l for first line, %L for last line)", "--source")
25 private var opt_sharedir = new OptionString("directory containing nitdoc assets", "--sharedir")
26 private var opt_shareurl = new OptionString("use shareurl instead of copy shared files", "--shareurl")
27 private var opt_custom_title = new OptionString("custom title for homepage", "--custom-title")
28 private var opt_custom_brand = new OptionString("custom link to external site", "--custom-brand")
29 private var opt_custom_intro = new OptionString("custom intro text for homepage", "--custom-overview-text")
30 private var opt_custom_footer = new OptionString("custom footer text", "--custom-footer-text")
31
32 private var opt_github_upstream = new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream")
33 private var opt_github_base_sha1 = new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1")
34 private var opt_github_gitdir = new OptionString("Git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir")
35
36 private var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: nitlanguage.org/piwik/)", "--piwik-tracker")
37 private var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id")
38
39 private var output_dir: String
40 private var min_visibility: MVisibility
41
42 redef init do
43 super
44
45 var opts = option_context
46 opts.add_option(opt_dir, opt_source, opt_sharedir, opt_shareurl)
47 opts.add_option(opt_custom_title, opt_custom_footer, opt_custom_intro, opt_custom_brand)
48 opts.add_option(opt_github_upstream, opt_github_base_sha1, opt_github_gitdir)
49 opts.add_option(opt_piwik_tracker, opt_piwik_site_id)
50 end
51
52 redef fun process_options(args) do
53 super
54
55 # output dir
56 var output_dir = opt_dir.value
57 if output_dir == null then
58 output_dir = "doc"
59 end
60 self.output_dir = output_dir
61 # github urls
62 var gh_upstream = opt_github_upstream.value
63 var gh_base_sha = opt_github_base_sha1.value
64 var gh_gitdir = opt_github_gitdir.value
65 if not gh_upstream == null or not gh_base_sha == null or not gh_gitdir == null then
66 if gh_upstream == null or gh_base_sha == null or gh_gitdir == null then
67 print "Error: Options {opt_github_upstream.names.first}, {opt_github_base_sha1.names.first} and {opt_github_gitdir.names.first} are required to enable the GitHub plugin"
68 abort
69 end
70 end
71 end
72 end
73
74 # The Nitdoc class explores the model and generate pages for each mentities found
75 class Nitdoc
76 var ctx: ToolContext
77 var model: Model
78 var mainmodule: MModule
79
80 private fun init_output_dir do
81 # create destination dir if it's necessary
82 var output_dir = ctx.output_dir
83 if not output_dir.file_exists then output_dir.mkdir
84 # locate share dir
85 var sharedir = ctx.opt_sharedir.value
86 if sharedir == null then
87 var dir = ctx.nit_dir
88 sharedir = dir/"share/nitdoc"
89 if not sharedir.file_exists then
90 print "Error: Cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
91 abort
92 end
93 end
94 # copy shared files
95 if ctx.opt_shareurl.value == null then
96 sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/* {output_dir.to_s.escape_to_sh}/")
97 else
98 sys.system("cp -r -- {sharedir.to_s.escape_to_sh}/resources/ {output_dir.to_s.escape_to_sh}/resources/")
99 end
100
101 end
102 end
103
104 # Nitdoc QuickSearch list generator
105 #
106 # Create a JSON object containing links to:
107 # * modules
108 # * mclasses
109 # * mpropdefs
110 # All entities are grouped by name to make the research easier.
111 class QuickSearch
112
113 private var table = new QuickSearchTable
114
115 var ctx: ToolContext
116 var model: Model
117
118 init do
119 for mmodule in model.mmodules do
120 if mmodule.is_fictive or mmodule.is_test_suite then continue
121 add_result_for(mmodule.name, mmodule.full_name, mmodule.nitdoc_url)
122 end
123 for mclass in model.mclasses do
124 if not ctx.filter_mclass(mclass) then continue
125 add_result_for(mclass.name, mclass.full_name, mclass.nitdoc_url)
126 end
127 for mproperty in model.mproperties do
128 if not ctx.filter_mproperty(mproperty) then continue
129 for mpropdef in mproperty.mpropdefs do
130 var full_name = mpropdef.mclassdef.mclass.full_name
131 var cls_url = mpropdef.mclassdef.mclass.nitdoc_url
132 var def_url = "{cls_url}#{mpropdef.mproperty.nitdoc_id}"
133 add_result_for(mproperty.name, full_name, def_url)
134 end
135 end
136 end
137
138 private fun add_result_for(query: String, txt: String, url: String) do
139 table[query].add new QuickSearchResult(txt, url)
140 end
141
142 fun render: Template do
143 var tpl = new Template
144 var buffer = new RopeBuffer
145 tpl.add buffer
146 buffer.append "var nitdocQuickSearchRawList="
147 table.append_json buffer
148 buffer.append ";"
149 return tpl
150 end
151 end
152
153 # The result map for QuickSearch.
154 private class QuickSearchTable
155 super JsonMapRead[String, QuickSearchResultList]
156 super HashMap[String, QuickSearchResultList]
157
158 redef fun provide_default_value(key) do
159 var v = new QuickSearchResultList
160 self[key] = v
161 return v
162 end
163 end
164
165 # A QuickSearch result list.
166 private class QuickSearchResultList
167 super JsonSequenceRead[QuickSearchResult]
168 super Array[QuickSearchResult]
169 end
170
171 # A QuickSearch result.
172 private class QuickSearchResult
173 super Jsonable
174
175 # The text of the link.
176 var txt: String
177
178 # The destination of the link.
179 var url: String
180
181 redef fun to_json do
182 return "\{\"txt\":{txt.to_json},\"url\":{url.to_json}\}"
183 end
184 end
185
186 # Nitdoc base page
187 # Define page structure and properties
188 abstract class NitdocPage
189
190 private var ctx: ToolContext
191 private var model: Model
192 private var mainmodule: MModule
193 private var name_sorter = new MEntityNameSorter
194
195 # Render the page as a html template
196 fun render: Template do
197 var shareurl = "."
198 if ctx.opt_shareurl.value != null then
199 shareurl = ctx.opt_shareurl.value.as(not null)
200 end
201
202 # build page
203 var tpl = tpl_page
204 tpl.title = tpl_title
205 tpl.url = page_url
206 tpl.shareurl = shareurl
207 tpl.topmenu = tpl_topmenu
208 tpl_content
209 tpl.footer = ctx.opt_custom_footer.value
210 tpl.body_attrs.add(new TagAttribute("data-bootstrap-share", shareurl))
211 tpl.sidebar = tpl_sidebar
212
213 # piwik tracking
214 var tracker_url = ctx.opt_piwik_tracker.value
215 var site_id = ctx.opt_piwik_site_id.value
216 if tracker_url != null and site_id != null then
217 tpl.scripts.add new TplPiwikScript(tracker_url, site_id)
218 end
219 return tpl
220 end
221
222 # URL to this page.
223 fun page_url: String is abstract
224
225 # Build page template
226 fun tpl_page: TplPage is abstract
227
228 # Build page sidebar if any
229 fun tpl_sidebar: nullable TplSidebar do return null
230
231 # Build page title string
232 fun tpl_title: String do
233 if ctx.opt_custom_title.value != null then
234 return ctx.opt_custom_title.value.to_s
235 end
236 return "Nitdoc"
237 end
238
239 # Build top menu template
240 fun tpl_topmenu: TplTopMenu do
241 var topmenu = new TplTopMenu(page_url)
242 var brand = ctx.opt_custom_brand.value
243 if brand != null then
244 var tpl = new Template
245 tpl.add "<span class='navbar-brand'>"
246 tpl.add brand
247 tpl.add "</span>"
248 topmenu.brand = tpl
249 end
250 topmenu.add_link new TplLink("index.html", "Overview")
251 topmenu.add_link new TplLink("search.html", "Index")
252 return topmenu
253 end
254
255 # Build page content template
256 fun tpl_content is abstract
257
258 # A (source) link template for a given location
259 fun tpl_showsource(location: nullable Location): nullable String
260 do
261 if location == null then return null
262 var source = ctx.opt_source.value
263 if source == null then
264 var url = location.file.filename.simplify_path
265 return "<a target='_blank' title='Show source' href=\"{url.html_escape}\">View Source</a>"
266 end
267 # THIS IS JUST UGLY ! (but there is no replace yet)
268 var x = source.split_with("%f")
269 source = x.join(location.file.filename.simplify_path)
270 x = source.split_with("%l")
271 source = x.join(location.line_start.to_s)
272 x = source.split_with("%L")
273 source = x.join(location.line_end.to_s)
274 source = source.simplify_path
275 return "<a target='_blank' title='Show source' href=\"{source.to_s.html_escape}\">View Source</a>"
276 end
277
278 # MProject description template
279 fun tpl_mproject_article(mproject: MProject): TplArticle do
280 var article = mproject.tpl_article
281 article.subtitle = mproject.tpl_declaration
282 article.content = mproject.tpl_definition
283 var mdoc = mproject.mdoc_or_fallback
284 if mdoc != null then
285 article.content = mdoc.tpl_short_comment
286 end
287 return article
288 end
289
290 # MGroup description template
291 fun tpl_mgroup_article(mgroup: MGroup): TplArticle do
292 var article = mgroup.tpl_article
293 article.subtitle = mgroup.tpl_declaration
294 article.content = mgroup.tpl_definition
295 return article
296 end
297
298 # MModule description template
299 fun tpl_mmodule_article(mmodule: MModule): TplArticle do
300 var article = mmodule.tpl_article
301 article.subtitle = mmodule.tpl_declaration
302 article.content = mmodule.tpl_definition
303 return article
304 end
305
306 # MClassDef description template
307 fun tpl_mclass_article(mclass: MClass, mclassdefs: Array[MClassDef]): TplArticle do
308 var article = mclass.tpl_article
309 return article
310 end
311
312 # MClassDef description template
313 fun tpl_mclassdef_article(mclassdef: MClassDef): TplArticle do
314 var article = mclassdef.tpl_article
315 if mclassdef.is_intro then article.content = null
316 article.source_link = tpl_showsource(mclassdef.location)
317 return article
318 end
319
320 # MProp description template
321 #
322 # `main_mpropdef`: The most important mpropdef to display
323 # `local_mpropdefs`: List of other locally defined mpropdefs to display
324 # `lin`: full linearization from local_mpropdefs to intro (displayed in redef tree)
325 fun tpl_mprop_article(main_mpropdef: MPropDef, local_mpropdefs: Array[MPropDef],
326 lin: Array[MPropDef]): TplArticle do
327 var mprop = main_mpropdef.mproperty
328 var article = new TplArticle(mprop.nitdoc_id)
329 var title = new Template
330 title.add mprop.tpl_icon
331 title.add "<span id='{main_mpropdef.nitdoc_id}'></span>"
332 if main_mpropdef.is_intro then
333 title.add mprop.tpl_link
334 title.add mprop.intro.tpl_signature
335 else
336 var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
337 var def_url = "{cls_url}#{mprop.nitdoc_id}"
338 var lnk = new TplLink.with_title(def_url, mprop.nitdoc_name,
339 "Go to introduction")
340 title.add "redef "
341 title.add lnk
342 end
343 article.title = title
344 article.title_classes.add "signature"
345 article.summary_title = "{mprop.nitdoc_name}"
346 article.subtitle = main_mpropdef.tpl_namespace
347 if main_mpropdef.mdoc_or_fallback != null then
348 article.content = main_mpropdef.mdoc_or_fallback.tpl_comment
349 end
350 var subarticle = new TplArticle("{main_mpropdef.nitdoc_id}.redefs")
351 # Add linearization
352 if lin.length > 1 then
353 var lin_article = new TplArticle("{main_mpropdef.nitdoc_id}.lin")
354 lin_article.title = "Inheritance"
355 var lst = new TplList.with_classes(["list-unstyled", "list-labeled"])
356 for mpropdef in lin do
357 lst.add_li mpropdef.tpl_inheritance_item
358 end
359 lin_article.content = lst
360 subarticle.add_child lin_article
361 end
362 article.add_child subarticle
363 return article
364 end
365 end
366
367 # The overview page
368 # Display a list of modules contained in program
369 class NitdocOverview
370 super NitdocPage
371
372 private var page = new TplPage
373 redef fun tpl_page do return page
374
375 private var sidebar = new TplSidebar
376 redef fun tpl_sidebar do return sidebar
377
378 redef fun tpl_title do
379 if ctx.opt_custom_title.value != null then
380 return ctx.opt_custom_title.value.to_s
381 else
382 return "Overview"
383 end
384 end
385
386 redef fun page_url do return "index.html"
387 end
388
389 # The search page
390 # Display a list of modules, classes and properties
391 class NitdocSearch
392 super NitdocPage
393
394 private var page = new TplPage
395 redef fun tpl_page do return page
396
397 redef fun tpl_title do return "Index"
398
399 redef fun page_url do return "search.html"
400 end
401
402 # A group page
403 # Display a flattened view of the group
404 class NitdocGroup
405 super NitdocPage
406
407 private var mgroup: MGroup
408
409 private var page = new TplPage
410 redef fun tpl_page do return page
411
412 private var sidebar = new TplSidebar
413 redef fun tpl_sidebar do return sidebar
414
415 redef fun tpl_title do return mgroup.nitdoc_name
416
417 redef fun page_url do return mgroup.nitdoc_url
418
419 redef fun tpl_topmenu do
420 var topmenu = super
421 var mproject = mgroup.mproject
422 if not mgroup.is_root then
423 topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name)
424 end
425 topmenu.add_link new TplLink(page_url, mproject.nitdoc_name)
426 return topmenu
427 end
428
429 # Class list to display in sidebar
430 fun tpl_sidebar_mclasses do
431 var mclasses = new HashSet[MClass]
432 mclasses.add_all intros
433 mclasses.add_all redefs
434 if mclasses.is_empty then return
435 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
436
437 var sorted = mclasses.to_a
438 name_sorter.sort(sorted)
439 for mclass in sorted do
440 list.add_li tpl_sidebar_item(mclass)
441 end
442 tpl_sidebar.boxes.add new TplSideBox.with_content("All classes", list)
443 end
444
445 private fun tpl_sidebar_item(def: MClass): TplListItem do
446 var classes = def.intro.tpl_css_classes.to_a
447 if intros.has(def) then
448 classes.add "intro"
449 else
450 classes.add "redef"
451 end
452 var lnk = new Template
453 lnk.add new TplLabel.with_classes(classes)
454 lnk.add def.tpl_link
455 return new TplListItem.with_content(lnk)
456 end
457 end
458
459 # A module page
460 # Display the list of introduced and redefined classes in module
461 class NitdocModule
462 super NitdocPage
463
464 private var mmodule: MModule
465
466 private var page = new TplPage
467 redef fun tpl_page do return page
468
469 private var sidebar = new TplSidebar
470 redef fun tpl_sidebar do return sidebar
471
472 redef fun tpl_title do return mmodule.nitdoc_name
473 redef fun page_url do return mmodule.nitdoc_url
474
475 redef fun tpl_topmenu do
476 var topmenu = super
477 var mproject = mmodule.mgroup.mproject
478 topmenu.add_link new TplLink(mproject.nitdoc_url, mproject.nitdoc_name)
479 topmenu.add_link new TplLink(page_url, mmodule.nitdoc_name)
480 return topmenu
481 end
482
483 # Class list to display in sidebar
484 fun tpl_sidebar_mclasses do
485 var mclasses = new HashSet[MClass]
486 mclasses.add_all mmodule.filter_intro_mclasses(ctx.min_visibility)
487 mclasses.add_all mmodule.filter_redef_mclasses(ctx.min_visibility)
488 if mclasses.is_empty then return
489 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
490
491 var sorted = mclasses.to_a
492 name_sorter.sort(sorted)
493 for mclass in sorted do
494 list.add_li tpl_sidebar_item(mclass)
495 end
496 tpl_sidebar.boxes.add new TplSideBox.with_content("All classes", list)
497 end
498
499 private fun tpl_sidebar_item(def: MClass): TplListItem do
500 var classes = def.intro.tpl_css_classes.to_a
501 if def.intro_mmodule == mmodule then
502 classes.add "intro"
503 else
504 classes.add "redef"
505 end
506 var lnk = new Template
507 lnk.add new TplLabel.with_classes(classes)
508 lnk.add def.tpl_link
509 return new TplListItem.with_content(lnk)
510 end
511 end
512
513 # A class page
514 # Display a list properties defined or redefined for this class
515 class NitdocClass
516 super NitdocPage
517
518 private var mclass: MClass
519
520 private var page = new TplPage
521 redef fun tpl_page do return page
522
523 private var sidebar = new TplSidebar
524 redef fun tpl_sidebar do return sidebar
525
526 redef fun tpl_title do return "{mclass.nitdoc_name}{mclass.tpl_signature.write_to_string}"
527 redef fun page_url do return mclass.nitdoc_url
528
529 redef fun tpl_topmenu do
530 var topmenu = super
531 var mproject = mclass.intro_mmodule.mgroup.mproject
532 topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}")
533 topmenu.add_link new TplLink(page_url, mclass.nitdoc_name)
534 return topmenu
535 end
536
537 # Property list to display in sidebar
538 fun tpl_sidebar_properties do
539 var by_kind = new PropertiesByKind.with_elements(mclass_inherited_mprops)
540 var summary = new TplList.with_classes(["list-unstyled"])
541
542 by_kind.sort_groups(name_sorter)
543 for g in by_kind.groups do tpl_sidebar_list(g, summary)
544 tpl_sidebar.boxes.add new TplSideBox.with_content("All properties", summary)
545 end
546
547 private fun tpl_sidebar_list(mprops: PropertyGroup[MProperty], summary: TplList) do
548 if mprops.is_empty then return
549 var entry = new TplListItem.with_content(mprops.title)
550 var list = new TplList.with_classes(["list-unstyled", "list-labeled"])
551 for mprop in mprops do
552 list.add_li tpl_sidebar_item(mprop)
553 end
554 entry.append list
555 summary.elts.add entry
556 end
557
558 private fun tpl_sidebar_item(mprop: MProperty): TplListItem do
559 var classes = mprop.intro.tpl_css_classes.to_a
560 if not mprops2mdefs.has_key(mprop) then
561 classes.add "inherit"
562 var cls_url = mprop.intro.mclassdef.mclass.nitdoc_url
563 var def_url = "{cls_url}#{mprop.nitdoc_id}"
564 var lnk = new TplLink(def_url, mprop.nitdoc_name)
565 var mdoc = mprop.intro.mdoc_or_fallback
566 if mdoc != null then lnk.title = mdoc.short_comment
567 var item = new Template
568 item.add new TplLabel.with_classes(classes)
569 item.add lnk
570 return new TplListItem.with_content(item)
571 end
572 var defs = mprops2mdefs[mprop]
573 if defs.has(mprop.intro) then
574 classes.add "intro"
575 else
576 classes.add "redef"
577 end
578 var lnk = new Template
579 lnk.add new TplLabel.with_classes(classes)
580 lnk.add mprop.tpl_anchor
581 return new TplListItem.with_content(lnk)
582 end
583 end
584
585 # A MProperty page
586 class NitdocProperty
587 super NitdocPage
588
589 private var mproperty: MProperty
590
591 private var page = new TplPage
592 redef fun tpl_page do return page
593
594 private var sidebar = new TplSidebar
595 redef fun tpl_sidebar do return sidebar
596
597 redef fun tpl_title do
598 return "{mproperty.nitdoc_name}{mproperty.tpl_signature.write_to_string}"
599 end
600
601 redef fun page_url do return mproperty.nitdoc_url
602
603 redef fun tpl_topmenu do
604 var topmenu = super
605 var mmodule = mproperty.intro_mclassdef.mmodule
606 var mproject = mmodule.mgroup.mproject
607 var mclass = mproperty.intro_mclassdef.mclass
608 topmenu.add_link new TplLink("{mproject.nitdoc_url}", "{mproject.nitdoc_name}")
609 topmenu.add_link new TplLink("{mclass.nitdoc_url}", "{mclass.nitdoc_name}")
610 topmenu.add_link new TplLink(page_url, mproperty.nitdoc_name)
611 return topmenu
612 end
613 end
614