1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
15 # Render the DocModel pages as HTML pages.
17 # FIXME this module is all f*cked up to maintain compatibility with
18 # the original `doc_templates` and `doc_model` modules.
19 # This will change in further refactorings.
23 import doc_hierarchies
24 import doc_intros_redefs
28 redef class ToolContext
30 # File pattern used to link documentation to source code.
31 var opt_source
= new OptionString("Format to link source code (%f for filename, " +
32 "%l for first line, %L for last line)", "--source")
34 # Use a shareurl instead of copy shared files.
36 # This is usefull if you don't want to store the Nitdoc templates with your
38 var opt_shareurl
= new OptionString("Use shareurl instead of copy shared files", "--shareurl")
40 # Use a custom title for the homepage.
41 var opt_custom_title
= new OptionString("Custom title for homepage", "--custom-title")
43 # Display a custom brand or logo in the documentation top menu.
44 var opt_custom_brand
= new OptionString("Custom link to external site", "--custom-brand")
46 # Display a custom introduction text before the packages overview.
47 var opt_custom_intro
= new OptionString("Custom intro text for homepage", "--custom-overview-text")
48 # Display a custom footer on each documentation page.
50 # Generally used to display the documentation or product version.
51 var opt_custom_footer
= new OptionString("Custom footer text", "--custom-footer-text")
55 # If you want to monitor your visitors.
56 var opt_piwik_tracker
= new OptionString("Piwik tracker URL (ex: `nitlanguage.org/piwik/`)", "--piwik-tracker")
58 # Piwik tracker site id.
59 var opt_piwik_site_id
= new OptionString("Piwik site ID", "--piwik-site-id")
61 # These options are not currently used in Nitdoc.
63 # FIXME redo the plugin
64 var opt_github_upstream
= new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream")
65 # FIXME redo the plugin
66 var opt_github_base_sha1
= new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1")
67 # FIXME redo the plugin
68 var opt_github_gitdir
= new OptionString("Git working directory used to resolve path name (ex: /home/me/mypackage/)", "--github-gitdir")
70 # Do not produce HTML files
71 var opt_no_render
= new OptionBool("Do not render HTML files", "--no-render")
76 option_context
.add_option
(
77 opt_source
, opt_share_dir
, opt_shareurl
, opt_custom_title
,
78 opt_custom_footer
, opt_custom_intro
, opt_custom_brand
,
79 opt_github_upstream
, opt_github_base_sha1
, opt_github_gitdir
,
80 opt_piwik_tracker
, opt_piwik_site_id
,
84 redef fun process_options
(args
) do
86 var upstream
= opt_github_upstream
87 var base_sha
= opt_github_base_sha1
88 var git_dir
= opt_github_gitdir
89 var opts
= [upstream
.value
, base_sha
.value
, git_dir
.value
]
90 if not opts
.has_only
(null) and opts
.has
(null) then
91 print
"Option Error: options {upstream.names.first}, " +
92 "{base_sha.names.first} and {git_dir.names.first} " +
93 "are required to enable the GitHub plugin"
99 # Render the Nitdoc as a HTML website.
100 class RenderHTMLPhase
103 # Used to sort sidebar elements by name.
104 var name_sorter
= new MEntityNameSorter
107 if ctx
.opt_no_render
.value
then return
109 for page
in doc
.pages
.values
do
110 page
.render
(self, doc
).write_to_file
("{ctx.output_dir.to_s}/{page.html_url}")
114 # Creates the output directory and imports assets files form `resources/`.
115 fun init_output_dir
do
116 # create destination dir if it's necessary
117 var output_dir
= ctx
.output_dir
118 if not output_dir
.file_exists
then output_dir
.mkdir
120 var sharedir
= ctx
.share_dir
/ "nitdoc"
122 if ctx
.opt_shareurl
.value
== null then
123 sys
.system
("cp -r -- {sharedir.to_s.escape_to_sh}/* {output_dir.to_s.escape_to_sh}/")
125 sys
.system
("cp -r -- {sharedir.to_s.escape_to_sh}/resources/ {output_dir.to_s.escape_to_sh}/resources/")
130 # Returns a HTML link for a given `location`.
131 fun html_source_link
(location
: nullable Location): nullable String
133 if location
== null then return null
134 var source
= ctx
.opt_source
.value
135 if source
== null then
136 var url
= location
.file
.filename
.simplify_path
137 return "<a target='_blank' title='Show source' href=\"{url.html_escape}\
">View Source</a>"
139 # THIS IS JUST UGLY ! (but there is no replace yet)
140 var x
= source
.split_with
("%f")
141 source
= x
.join
(location
.file
.filename
.simplify_path
)
142 x
= source
.split_with
("%l")
143 source
= x
.join
(location
.line_start
.to_s
)
144 x
= source
.split_with
("%L")
145 source
= x
.join
(location
.line_end
.to_s
)
146 source
= source
.simplify_path
147 return "<a target='_blank' title='Show source' href=\"{source.to_s.html_escape}\
">View Source</a>"
153 # Render the page as a html template.
154 private fun render
(v
: RenderHTMLPhase, doc
: DocModel): Writable do
156 if v
.ctx
.opt_shareurl
.value
!= null then
157 shareurl
= v
.ctx
.opt_shareurl
.value
.as(not null)
161 self.shareurl
= shareurl
162 self.footer
= v
.ctx
.opt_custom_footer
.value
163 self.body_attrs
.add
(new TagAttribute("data-bootstrap-share", shareurl
))
172 var tracker_url
= v
.ctx
.opt_piwik_tracker
.value
173 var site_id
= v
.ctx
.opt_piwik_site_id
.value
174 if tracker_url
!= null and site_id
!= null then
175 self.scripts
.add
new TplPiwikScript(tracker_url
, site_id
)
181 # all properties below are roughly copied from `doc_pages`
183 # Build page title string
184 fun init_title
(v
: RenderHTMLPhase, doc
: DocModel) do end
186 # Build top menu template if any.
187 fun init_topmenu
(v
: RenderHTMLPhase, doc
: DocModel) do
188 topmenu
= new DocTopMenu
189 topmenu
.brand
= v
.ctx
.opt_custom_brand
.value
190 var title
= "Overview"
191 if v
.ctx
.opt_custom_title
.value
!= null then
192 title
= v
.ctx
.opt_custom_title
.value
.to_s
194 topmenu
.add_li
new ListItem(new Link("index.html", title
))
195 topmenu
.add_li
new ListItem(new Link("search.html", "Index"))
196 topmenu
.active_item
= topmenu
.items
.first
199 # Build page sidebar if any.
200 fun init_sidebar
(v
: RenderHTMLPhase, doc
: DocModel) do
201 sidebar
= new DocSideBar
202 sidebar
.boxes
.add
new DocSideBox("Summary", html_toc
)
205 # Build page content template.
206 fun init_content
(v
: RenderHTMLPhase, doc
: DocModel) do
207 root
.init_html_render
(v
, doc
, self)
211 redef class OverviewPage
212 redef var html_url
= "index.html"
214 redef fun init_title
(v
, doc
) do
216 if v
.ctx
.opt_custom_title
.value
!= null then
217 title
= v
.ctx
.opt_custom_title
.value
.to_s
222 redef class SearchPage
223 redef var html_url
= "search.html"
224 redef fun init_title
(v
, doc
) do title
= "Index"
226 redef fun init_topmenu
(v
, doc
) do
228 topmenu
.active_item
= topmenu
.items
.last
231 redef fun init_sidebar
(v
, doc
) do end
234 redef class MEntityPage
235 redef var html_url
is lazy
do
236 if mentity
isa MGroup and mentity
.mdoc
!= null then
237 return "api_{mentity.nitdoc_url}"
239 return mentity
.nitdoc_url
242 redef fun init_title
(v
, doc
) do title
= mentity
.html_name
245 # FIXME all clases below are roughly copied from `doc_pages` and adapted to new
246 # doc phases. This is to preserve the compatibility with the current
247 # `doc_templates` module.
249 redef class ReadmePage
250 redef var html_url
is lazy
do return mentity
.nitdoc_url
252 redef fun init_topmenu
(v
, doc
) do
254 var mpackage
= mentity
.mpackage
255 if not mentity
.is_root
then
256 topmenu
.add_li
new ListItem(new Link(mpackage
.nitdoc_url
, mpackage
.html_name
))
258 topmenu
.add_li
new ListItem(new Link(html_url
, mpackage
.html_name
))
259 topmenu
.active_item
= topmenu
.items
.last
262 redef fun init_sidebar
(v
, doc
) do
264 var api_lnk
= """<a href="api_{{{mentity.nitdoc_url}}}">Go to API</a>"""
265 sidebar
.boxes
.unshift
new DocSideBox(api_lnk
, "")
269 redef class MGroupPage
270 redef fun init_topmenu
(v
, doc
) do
272 var mpackage
= mentity
.mpackage
273 if not mentity
.is_root
then
274 topmenu
.add_li
new ListItem(new Link(mpackage
.nitdoc_url
, mpackage
.html_name
))
276 topmenu
.add_li
new ListItem(new Link(html_url
, mpackage
.html_name
))
277 topmenu
.active_item
= topmenu
.items
.last
280 redef fun init_sidebar
(v
, doc
) do
283 if mentity
.mdoc
!= null then
284 var doc_lnk
= """<a href="{{{mentity.nitdoc_url}}}">Go to README</a>"""
285 sidebar
.boxes
.unshift
new DocSideBox(doc_lnk
, "")
288 var mclasses
= new HashSet[MClass]
289 mclasses
.add_all intros
290 mclasses
.add_all redefs
291 if mclasses
.is_empty
then return
292 var list
= new UnorderedList
293 list
.css_classes
.add
"list-unstyled list-labeled"
294 var sorted
= mclasses
.to_a
295 v
.name_sorter
.sort
(sorted
)
296 for mclass
in sorted
do
297 list
.add_li tpl_sidebar_item
(mclass
)
299 sidebar
.boxes
.add
new DocSideBox("All classes", list
)
300 sidebar
.boxes
.last
.is_open
= false
303 private fun tpl_sidebar_item
(def
: MClass): ListItem do
304 var classes
= def
.intro
.css_classes
305 if intros
.has
(def
) then
310 var lnk
= new Template
311 lnk
.add
new DocHTMLLabel.with_classes
(classes
)
312 lnk
.add def
.html_link
313 return new ListItem(lnk
)
317 redef class MModulePage
318 redef fun init_topmenu
(v
, doc
) do
320 var mpackage
= mentity
.mpackage
321 if mpackage
!= null then
322 topmenu
.add_li
new ListItem(new Link(mpackage
.nitdoc_url
, mpackage
.html_name
))
324 topmenu
.add_li
new ListItem(new Link(mentity
.nitdoc_url
, mentity
.html_name
))
325 topmenu
.active_item
= topmenu
.items
.last
328 # Class list to display in sidebar
329 redef fun init_sidebar
(v
, doc
) do
332 var mclasses
= new HashSet[MClass]
333 mclasses
.add_all mentity
.collect_intro_mclasses
(v
.doc
)
334 mclasses
.add_all mentity
.collect_redef_mclasses
(v
.doc
)
335 if mclasses
.is_empty
then return
336 var list
= new UnorderedList
337 list
.css_classes
.add
"list-unstyled list-labeled"
339 var sorted
= mclasses
.to_a
340 v
.name_sorter
.sort
(sorted
)
341 for mclass
in sorted
do
342 list
.add_li tpl_sidebar_item
(mclass
)
344 sidebar
.boxes
.add
new DocSideBox("All classes", list
)
345 sidebar
.boxes
.last
.is_open
= false
348 private fun tpl_sidebar_item
(def
: MClass): ListItem do
349 var classes
= def
.intro
.css_classes
350 if def
.intro_mmodule
== self.mentity
then
355 var lnk
= new Template
356 lnk
.add
new DocHTMLLabel.with_classes
(classes
)
357 lnk
.add def
.html_link
358 return new ListItem(lnk
)
362 redef class MClassPage
364 redef fun init_topmenu
(v
, doc
) do
366 var mpackage
= mentity
.intro_mmodule
.mgroup
.mpackage
367 topmenu
.add_li
new ListItem(new Link(mpackage
.nitdoc_url
, mpackage
.html_name
))
368 topmenu
.add_li
new ListItem(new Link(html_url
, mentity
.html_name
))
369 topmenu
.active_item
= topmenu
.items
.last
372 redef fun init_sidebar
(v
, doc
) do
374 var by_kind
= new PropertiesByKind.with_elements
(mclass_inherited_mprops
(v
, doc
))
375 var summary
= new UnorderedList
376 summary
.css_classes
.add
"list-unstyled"
378 by_kind
.sort_groups
(v
.name_sorter
)
379 for g
in by_kind
.groups
do tpl_sidebar_list
(g
, summary
)
380 sidebar
.boxes
.add
new DocSideBox("All properties", summary
)
381 sidebar
.boxes
.last
.is_open
= false
384 private fun tpl_sidebar_list
(mprops
: PropertyGroup[MProperty], summary
: UnorderedList) do
385 if mprops
.is_empty
then return
386 var list
= new UnorderedList
387 list
.css_classes
.add
"list-unstyled list-labeled"
388 for mprop
in mprops
do
389 list
.add_li tpl_sidebar_item
(mprop
)
391 var content
= new Template
392 content
.add mprops
.title
394 var li
= new ListItem(content
)
398 private fun tpl_sidebar_item
(mprop
: MProperty): ListItem do
399 var classes
= mprop
.intro
.css_classes
400 if not mprop_is_local
(mprop
) then
401 classes
.add
"inherit"
402 var cls_url
= mprop
.intro
.mclassdef
.mclass
.nitdoc_url
403 var def_url
= "{cls_url}#{mprop.nitdoc_id}.definition"
404 var lnk
= new Link(def_url
, mprop
.html_name
)
405 var mdoc
= mprop
.intro
.mdoc_or_fallback
406 if mdoc
!= null then lnk
.title
= mdoc
.synopsis
407 var item
= new Template
408 item
.add
new DocHTMLLabel.with_classes
(classes
)
410 return new ListItem(item
)
412 if mpropdefs
.has
(mprop
.intro
) then
417 var def
= select_mpropdef
(mprop
)
418 var anc
= def
.html_link_to_anchor
419 anc
.href
= "#{def.nitdoc_id}.definition"
420 var lnk
= new Template
421 lnk
.add
new DocHTMLLabel.with_classes
(classes
)
423 return new ListItem(lnk
)
426 # Get the mpropdef contained in `self` page for a mprop.
428 # FIXME this method is used to translate a mprop into a mpropdefs for
429 # section linking. A better page structure should avoid this...
430 private fun select_mpropdef
(mprop
: MProperty): MPropDef do
431 for mclassdef
in mentity
.mclassdefs
do
432 for mpropdef
in mclassdef
.mpropdefs
do
433 if mpropdef
.mproperty
== mprop
then return mpropdef
436 abort # FIXME is there a case where the prop is not found?
439 private fun mclass_inherited_mprops
(v
: RenderHTMLPhase, doc
: DocModel): Set[MProperty] do
440 var res
= new HashSet[MProperty]
441 var local
= mentity
.collect_local_mproperties
(v
.doc
)
442 for mprop
in mentity
.collect_inherited_mproperties
(v
.doc
) do
443 if local
.has
(mprop
) then continue
444 #if mprop isa MMethod and mprop.is_init then continue
445 if mprop
.intro
.mclassdef
.mclass
.name
== "Object" and
446 (mprop
.visibility
== protected_visibility
or
447 mprop
.intro
.mclassdef
.mmodule
.name
!= "kernel") then continue
454 private fun mprop_is_local
(mprop
: MProperty): Bool do
455 for mpropdef
in mprop
.mpropdefs
do
456 if self.mpropdefs
.has
(mpropdef
) then return true
462 redef class MPropertyPage
463 redef fun init_title
(v
, doc
) do
464 title
= "{mentity.html_name}{mentity.html_short_signature.write_to_string}"
467 redef fun init_topmenu
(v
, doc
) do
469 var mmodule
= mentity
.intro_mclassdef
.mmodule
470 var mpackage
= mmodule
.mgroup
.mpackage
471 var mclass
= mentity
.intro_mclassdef
.mclass
472 topmenu
.add_li
new ListItem(new Link(mpackage
.nitdoc_url
, mpackage
.html_name
))
473 topmenu
.add_li
new ListItem(new Link(mclass
.nitdoc_url
, mclass
.html_name
))
474 topmenu
.add_li
new ListItem(new Link(html_url
, mentity
.html_name
))
475 topmenu
.active_item
= topmenu
.items
.last
479 redef class DocComposite
480 # Prepares the HTML rendering for this element.
482 # This visit is mainly used to set template attributes before rendering.
483 fun init_html_render
(v
: RenderHTMLPhase, doc
: DocModel, page
: DocPage) do
484 for child
in children
do child
.init_html_render
(v
, doc
, page
)
488 # FIXME hideous hacks to avoid diff
489 redef class MEntitySection
490 redef fun init_html_render
(v
, doc
, page
) do
491 if not page
isa MEntityPage then return
492 var mentity
= self.mentity
493 if mentity
isa MGroup and mentity
.is_root
then
494 html_title
= mentity
.mpackage
.html_name
495 html_subtitle
= mentity
.mpackage
.html_declaration
496 else if mentity
isa MProperty then
497 var title
= new Template
498 title
.add mentity
.html_name
499 title
.add mentity
.html_signature
501 html_subtitle
= mentity
.html_namespace
502 html_toc_title
= mentity
.html_name
508 # FIXME hideous hacks to avoid diff
509 redef class ConcernSection
510 redef fun init_html_render
(v
, doc
, page
) do
511 if not page
isa MEntityPage then return
512 var mentity
= self.mentity
513 if page
isa MGroupPage then
515 html_toc_title
= mentity
.html_name
516 is_toc_hidden
= false
517 else if page
.mentity
isa MModule and mentity
isa MModule then
518 var title
= new Template
519 if mentity
== page
.mentity
then
521 html_toc_title
= "in {mentity.html_name}"
524 html_toc_title
= "from {mentity.html_name}"
526 title
.add mentity
.html_namespace
528 else if (page
.mentity
isa MClass and mentity
isa MModule) or
529 (page
.mentity
isa MProperty and mentity
isa MModule) then
530 var title
= new Template
532 title
.add mentity
.html_namespace
534 html_toc_title
= "in {mentity.html_name}"
541 redef class IntroArticle
542 redef fun init_html_render
(v
, doc
, page
) do
543 var mentity
= self.mentity
544 if mentity
isa MModule then
545 html_source_link
= v
.html_source_link
(mentity
.location
)
546 else if mentity
isa MClassDef then
547 html_source_link
= v
.html_source_link
(mentity
.location
)
548 else if mentity
isa MPropDef then
549 html_source_link
= v
.html_source_link
(mentity
.location
)
554 # FIXME less hideous hacks...
555 redef class DefinitionArticle
556 redef fun init_html_render
(v
, doc
, page
) do
557 var mentity
= self.mentity
558 if mentity
isa MPackage or mentity
isa MModule then
559 var title
= new Template
560 title
.add mentity
.html_icon
561 title
.add mentity
.html_namespace
563 html_toc_title
= mentity
.html_name
564 if mentity
isa MModule then
565 html_source_link
= v
.html_source_link
(mentity
.location
)
567 else if mentity
isa MClassDef then
568 var title
= new Template
570 title
.add mentity
.mmodule
.html_namespace
571 html_title
= mentity
.html_declaration
572 html_subtitle
= title
573 html_toc_title
= "in {mentity.html_name}"
574 html_source_link
= v
.html_source_link
(mentity
.location
)
575 if page
isa MEntityPage and mentity
.is_intro
and mentity
.mmodule
!= page
.mentity
then
576 is_short_comment
= true
578 if page
isa MModulePage then is_toc_hidden
= true
579 else if mentity
isa MPropDef then
580 if page
isa MClassPage then
581 var title
= new Template
582 title
.add mentity
.html_icon
583 title
.add mentity
.html_declaration
585 html_subtitle
= mentity
.html_namespace
586 html_toc_title
= mentity
.html_name
588 var title
= new Template
590 title
.add mentity
.mclassdef
.html_link
592 html_toc_title
= "in {mentity.mclassdef.html_name}"
594 html_source_link
= v
.html_source_link
(mentity
.location
)
596 if page
isa MGroupPage and mentity
isa MModule then
603 redef class HomeArticle
604 redef fun init_html_render
(v
, doc
, page
) do
605 if v
.ctx
.opt_custom_title
.value
!= null then
606 self.html_title
= v
.ctx
.opt_custom_title
.value
.to_s
607 self.html_toc_title
= v
.ctx
.opt_custom_title
.value
.to_s
609 self.content
= v
.ctx
.opt_custom_intro
.value
614 redef class GraphArticle
615 redef fun init_html_render
(v
, doc
, page
) do
616 var path
= v
.ctx
.output_dir
/ graph_id
617 var file
= new FileWriter.open
("{path}.dot")
620 var proc
= new ProcessReader("dot", "-Tsvg", "-Tcmapx", "{path}.dot")
623 while not proc
.eof
do
625 if i
< 6 then continue # skip dot default header
626 svg
.append proc
.read_line
629 self.svg
= svg
.write_to_string