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("link for source (%f for filename, " +
32 "%l for first line, %L for last line)", "--source")
34 # Directory where the CSS and JS is stored.
35 var opt_sharedir
= new OptionString("directory containing nitdoc assets", "--sharedir")
37 # Use a shareurl instead of copy shared files.
39 # This is usefull if you don't want to store the Nitdoc templates with your
41 var opt_shareurl
= new OptionString("use shareurl instead of copy shared files", "--shareurl")
43 # Use a custom title for the homepage.
44 var opt_custom_title
= new OptionString("custom title for homepage", "--custom-title")
46 # Display a custom brand or logo in the documentation top menu.
47 var opt_custom_brand
= new OptionString("custom link to external site", "--custom-brand")
49 # Display a custom introduction text before the projects overview.
50 var opt_custom_intro
= new OptionString("custom intro text for homepage", "--custom-overview-text")
51 # Display a custom footer on each documentation page.
53 # Generally used to display the documentation or product version.
54 var opt_custom_footer
= new OptionString("custom footer text", "--custom-footer-text")
58 # If you want to monitor your visitors.
59 var opt_piwik_tracker
= new OptionString("Piwik tracker URL (ex: nitlanguage.org/piwik/)", "--piwik-tracker")
61 # Piwik tracker site id.
62 var opt_piwik_site_id
= new OptionString("Piwik site ID", "--piwik-site-id")
64 # These options are not currently used in Nitdoc.
66 # FIXME redo the plugin
67 var opt_github_upstream
= new OptionString("Git branch where edited commits will be pulled into (ex: user:repo:branch)", "--github-upstream")
68 # FIXME redo the plugin
69 var opt_github_base_sha1
= new OptionString("Git sha1 of base commit used to create pull request", "--github-base-sha1")
70 # FIXME redo the plugin
71 var opt_github_gitdir
= new OptionString("Git working directory used to resolve path name (ex: /home/me/myproject/)", "--github-gitdir")
76 option_context
.add_option
(
77 opt_source
, opt_sharedir
, 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
)
83 redef fun process_options
(args
) do
85 var upstream
= opt_github_upstream
86 var base_sha
= opt_github_base_sha1
87 var git_dir
= opt_github_gitdir
88 var opts
= [upstream
.value
, base_sha
.value
, git_dir
.value
]
89 if not opts
.has_only
(null) and opts
.has
(null) then
90 print
"Option Error: options {upstream.names.first}, " +
91 "{base_sha.names.first} and {git_dir.names.first} " +
92 "are required to enable the GitHub plugin"
98 # Render the Nitdoc as a HTML website.
102 # Used to sort sidebar elements by name.
103 var name_sorter
= new MEntityNameSorter
107 for page
in doc
.pages
.values
do
108 page
.render
(self, doc
).write_to_file
("{ctx.output_dir.to_s}/{page.html_url}")
112 # Creates the output directory and imports assets files form `resources/`.
113 fun init_output_dir
do
114 # create destination dir if it's necessary
115 var output_dir
= ctx
.output_dir
116 if not output_dir
.file_exists
then output_dir
.mkdir
118 var sharedir
= ctx
.opt_sharedir
.value
119 if sharedir
== null then
120 var dir
= ctx
.nit_dir
121 sharedir
= dir
/"share/nitdoc"
122 if not sharedir
.file_exists
then
123 print
"Error: cannot locate nitdoc share files. Uses --sharedir or envvar NIT_DIR"
128 if ctx
.opt_shareurl
.value
== null then
129 sys
.system
("cp -r -- {sharedir.to_s.escape_to_sh}/* {output_dir.to_s.escape_to_sh}/")
131 sys
.system
("cp -r -- {sharedir.to_s.escape_to_sh}/resources/ {output_dir.to_s.escape_to_sh}/resources/")
136 # Returns a HTML link for a given `location`.
137 fun html_source_link
(location
: nullable Location): nullable String
139 if location
== null then return null
140 var source
= ctx
.opt_source
.value
141 if source
== null then
142 var url
= location
.file
.filename
.simplify_path
143 return "<a target='_blank' title='Show source' href=\"{url.html_escape}\
">View Source</a>"
145 # THIS IS JUST UGLY ! (but there is no replace yet)
146 var x
= source
.split_with
("%f")
147 source
= x
.join
(location
.file
.filename
.simplify_path
)
148 x
= source
.split_with
("%l")
149 source
= x
.join
(location
.line_start
.to_s
)
150 x
= source
.split_with
("%L")
151 source
= x
.join
(location
.line_end
.to_s
)
152 source
= source
.simplify_path
153 return "<a target='_blank' title='Show source' href=\"{source.to_s.html_escape}\
">View Source</a>"
159 # Render the page as a html template.
160 private fun render
(v
: RenderHTMLPhase, doc
: DocModel): Writable do
162 if v
.ctx
.opt_shareurl
.value
!= null then
163 shareurl
= v
.ctx
.opt_shareurl
.value
.as(not null)
167 self.shareurl
= shareurl
168 self.footer
= v
.ctx
.opt_custom_footer
.value
169 self.body_attrs
.add
(new TagAttribute("data-bootstrap-share", shareurl
))
178 var tracker_url
= v
.ctx
.opt_piwik_tracker
.value
179 var site_id
= v
.ctx
.opt_piwik_site_id
.value
180 if tracker_url
!= null and site_id
!= null then
181 self.scripts
.add
new TplPiwikScript(tracker_url
, site_id
)
187 # all properties below are roughly copied from `doc_pages`
189 # Build page title string
190 fun init_title
(v
: RenderHTMLPhase, doc
: DocModel) do end
192 # Build top menu template if any.
193 fun init_topmenu
(v
: RenderHTMLPhase, doc
: DocModel) do
194 topmenu
= new DocTopMenu
195 topmenu
.brand
= v
.ctx
.opt_custom_brand
.value
196 var title
= "Overview"
197 if v
.ctx
.opt_custom_title
.value
!= null then
198 title
= v
.ctx
.opt_custom_title
.value
.to_s
200 topmenu
.add_li
new ListItem(new Link("index.html", title
))
201 topmenu
.add_li
new ListItem(new Link("search.html", "Index"))
202 topmenu
.active_item
= topmenu
.items
.first
205 # Build page sidebar if any.
206 fun init_sidebar
(v
: RenderHTMLPhase, doc
: DocModel) do
207 sidebar
= new DocSideBar
208 sidebar
.boxes
.add
new DocSideBox("Summary", html_toc
)
211 # Build page content template.
212 fun init_content
(v
: RenderHTMLPhase, doc
: DocModel) do
213 root
.init_html_render
(v
, doc
, self)
217 redef class OverviewPage
218 redef var html_url
= "index.html"
220 redef fun init_title
(v
, doc
) do
222 if v
.ctx
.opt_custom_title
.value
!= null then
223 title
= v
.ctx
.opt_custom_title
.value
.to_s
228 redef class SearchPage
229 redef var html_url
= "search.html"
230 redef fun init_title
(v
, doc
) do title
= "Index"
232 redef fun init_topmenu
(v
, doc
) do
234 topmenu
.active_item
= topmenu
.items
.last
237 redef fun init_sidebar
(v
, doc
) do end
240 redef class MEntityPage
241 redef var html_url
is lazy
do 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 MGroupPage
250 redef fun init_topmenu
(v
, doc
) do
252 var mproject
= mentity
.mproject
253 if not mentity
.is_root
then
254 topmenu
.add_li
new ListItem(new Link(mproject
.nitdoc_url
, mproject
.html_name
))
256 topmenu
.add_li
new ListItem(new Link(html_url
, mproject
.html_name
))
257 topmenu
.active_item
= topmenu
.items
.last
260 redef fun init_sidebar
(v
, doc
) do
262 var mclasses
= new HashSet[MClass]
263 mclasses
.add_all intros
264 mclasses
.add_all redefs
265 if mclasses
.is_empty
then return
266 var list
= new UnorderedList
267 list
.css_classes
.add
"list-unstyled list-labeled"
268 var sorted
= mclasses
.to_a
269 v
.name_sorter
.sort
(sorted
)
270 for mclass
in sorted
do
271 list
.add_li tpl_sidebar_item
(mclass
)
273 sidebar
.boxes
.add
new DocSideBox("All classes", list
)
274 sidebar
.boxes
.last
.is_open
= false
277 private fun tpl_sidebar_item
(def
: MClass): ListItem do
278 var classes
= def
.intro
.css_classes
279 if intros
.has
(def
) then
284 var lnk
= new Template
285 lnk
.add
new DocHTMLLabel.with_classes
(classes
)
286 lnk
.add def
.html_link
287 return new ListItem(lnk
)
291 redef class MModulePage
292 redef fun init_topmenu
(v
, doc
) do
294 var mproject
= mentity
.mproject
295 topmenu
.add_li
new ListItem(new Link(mproject
.nitdoc_url
, mproject
.html_name
))
296 topmenu
.add_li
new ListItem(new Link(mentity
.nitdoc_url
, mentity
.html_name
))
297 topmenu
.active_item
= topmenu
.items
.last
300 # Class list to display in sidebar
301 redef fun init_sidebar
(v
, doc
) do
304 var mclasses
= new HashSet[MClass]
305 mclasses
.add_all mentity
.collect_intro_mclasses
(v
.ctx
.min_visibility
)
306 mclasses
.add_all mentity
.collect_redef_mclasses
(v
.ctx
.min_visibility
)
307 if mclasses
.is_empty
then return
308 var list
= new UnorderedList
309 list
.css_classes
.add
"list-unstyled list-labeled"
311 var sorted
= mclasses
.to_a
312 v
.name_sorter
.sort
(sorted
)
313 for mclass
in sorted
do
314 list
.add_li tpl_sidebar_item
(mclass
)
316 sidebar
.boxes
.add
new DocSideBox("All classes", list
)
317 sidebar
.boxes
.last
.is_open
= false
320 private fun tpl_sidebar_item
(def
: MClass): ListItem do
321 var classes
= def
.intro
.css_classes
322 if def
.intro_mmodule
== self.mentity
then
327 var lnk
= new Template
328 lnk
.add
new DocHTMLLabel.with_classes
(classes
)
329 lnk
.add def
.html_link
330 return new ListItem(lnk
)
334 redef class MClassPage
336 redef fun init_topmenu
(v
, doc
) do
338 var mproject
= mentity
.intro_mmodule
.mgroup
.mproject
339 topmenu
.add_li
new ListItem(new Link(mproject
.nitdoc_url
, mproject
.html_name
))
340 topmenu
.add_li
new ListItem(new Link(html_url
, mentity
.html_name
))
341 topmenu
.active_item
= topmenu
.items
.last
344 redef fun init_sidebar
(v
, doc
) do
346 var by_kind
= new PropertiesByKind.with_elements
(mclass_inherited_mprops
(v
, doc
))
347 var summary
= new UnorderedList
348 summary
.css_classes
.add
"list-unstyled"
350 by_kind
.sort_groups
(v
.name_sorter
)
351 for g
in by_kind
.groups
do tpl_sidebar_list
(g
, summary
)
352 sidebar
.boxes
.add
new DocSideBox("All properties", summary
)
353 sidebar
.boxes
.last
.is_open
= false
356 private fun tpl_sidebar_list
(mprops
: PropertyGroup[MProperty], summary
: UnorderedList) do
357 if mprops
.is_empty
then return
358 var list
= new UnorderedList
359 list
.css_classes
.add
"list-unstyled list-labeled"
360 for mprop
in mprops
do
361 list
.add_li tpl_sidebar_item
(mprop
)
363 var content
= new Template
364 content
.add mprops
.title
366 var li
= new ListItem(content
)
370 private fun tpl_sidebar_item
(mprop
: MProperty): ListItem do
371 var classes
= mprop
.intro
.css_classes
372 if not mprop_is_local
(mprop
) then
373 classes
.add
"inherit"
374 var cls_url
= mprop
.intro
.mclassdef
.mclass
.nitdoc_url
375 var def_url
= "{cls_url}#{mprop.nitdoc_id}.definition"
376 var lnk
= new Link(def_url
, mprop
.html_name
)
377 var mdoc
= mprop
.intro
.mdoc_or_fallback
378 if mdoc
!= null then lnk
.title
= mdoc
.short_comment
379 var item
= new Template
380 item
.add
new DocHTMLLabel.with_classes
(classes
)
382 return new ListItem(item
)
384 if mpropdefs
.has
(mprop
.intro
) then
389 var def
= select_mpropdef
(mprop
)
390 var anc
= def
.html_link_to_anchor
391 anc
.href
= "#{def.nitdoc_id}.definition"
392 var lnk
= new Template
393 lnk
.add
new DocHTMLLabel.with_classes
(classes
)
395 return new ListItem(lnk
)
398 # Get the mpropdef contained in `self` page for a mprop.
400 # FIXME this method is used to translate a mprop into a mpropdefs for
401 # section linking. A better page structure should avoid this...
402 private fun select_mpropdef
(mprop
: MProperty): MPropDef do
403 for mclassdef
in mentity
.mclassdefs
do
404 for mpropdef
in mclassdef
.mpropdefs
do
405 if mpropdef
.mproperty
== mprop
then return mpropdef
408 abort # FIXME is there a case where the prop is not found?
411 private fun mclass_inherited_mprops
(v
: RenderHTMLPhase, doc
: DocModel): Set[MProperty] do
412 var res
= new HashSet[MProperty]
413 var local
= mentity
.collect_local_mproperties
(v
.ctx
.min_visibility
)
414 for mprop
in mentity
.collect_inherited_mproperties
(v
.ctx
.min_visibility
) do
415 if local
.has
(mprop
) then continue
416 #if mprop isa MMethod and mprop.is_init then continue
417 if mprop
.intro
.mclassdef
.mclass
.name
== "Object" and
418 (mprop
.visibility
== protected_visibility
or
419 mprop
.intro
.mclassdef
.mmodule
.name
!= "kernel") then continue
426 private fun mprop_is_local
(mprop
: MProperty): Bool do
427 for mpropdef
in mprop
.mpropdefs
do
428 if self.mpropdefs
.has
(mpropdef
) then return true
434 redef class MPropertyPage
435 redef fun init_title
(v
, doc
) do
436 title
= "{mentity.html_name}{mentity.html_short_signature.write_to_string}"
439 redef fun init_topmenu
(v
, doc
) do
441 var mmodule
= mentity
.intro_mclassdef
.mmodule
442 var mproject
= mmodule
.mgroup
.mproject
443 var mclass
= mentity
.intro_mclassdef
.mclass
444 topmenu
.add_li
new ListItem(new Link(mproject
.nitdoc_url
, mproject
.html_name
))
445 topmenu
.add_li
new ListItem(new Link(mclass
.nitdoc_url
, mclass
.html_name
))
446 topmenu
.add_li
new ListItem(new Link(html_url
, mentity
.html_name
))
447 topmenu
.active_item
= topmenu
.items
.last
451 redef class DocComposite
452 # Prepares the HTML rendering for this element.
454 # This visit is mainly used to set template attributes before rendering.
455 fun init_html_render
(v
: RenderHTMLPhase, doc
: DocModel, page
: DocPage) do
456 for child
in children
do child
.init_html_render
(v
, doc
, page
)
460 # FIXME hideous hacks to avoid diff
461 redef class MEntitySection
462 redef fun init_html_render
(v
, doc
, page
) do
463 if not page
isa MEntityPage then return
464 var mentity
= self.mentity
465 if mentity
isa MGroup and mentity
.is_root
then
466 html_title
= mentity
.mproject
.html_name
467 html_subtitle
= mentity
.mproject
.html_declaration
468 else if mentity
isa MProperty then
469 var title
= new Template
470 title
.add mentity
.html_name
471 title
.add mentity
.html_signature
473 html_subtitle
= mentity
.html_namespace
474 html_toc_title
= mentity
.html_name
480 # FIXME hideous hacks to avoid diff
481 redef class ConcernSection
482 redef fun init_html_render
(v
, doc
, page
) do
483 if not page
isa MEntityPage then return
484 var mentity
= self.mentity
485 if page
isa MGroupPage then
487 html_toc_title
= mentity
.html_name
488 is_toc_hidden
= false
489 else if page
.mentity
isa MModule and mentity
isa MModule then
490 var title
= new Template
491 if mentity
== page
.mentity
then
493 html_toc_title
= "in {mentity.html_name}"
496 html_toc_title
= "from {mentity.html_name}"
498 title
.add mentity
.html_namespace
500 else if (page
.mentity
isa MClass and mentity
isa MModule) or
501 (page
.mentity
isa MProperty and mentity
isa MModule) then
502 var title
= new Template
504 title
.add mentity
.html_namespace
506 html_toc_title
= "in {mentity.html_name}"
513 redef class IntroArticle
514 redef fun init_html_render
(v
, doc
, page
) do
515 var mentity
= self.mentity
516 if mentity
isa MModule then
517 html_source_link
= v
.html_source_link
(mentity
.location
)
518 else if mentity
isa MClassDef then
519 html_source_link
= v
.html_source_link
(mentity
.location
)
520 else if mentity
isa MPropDef then
521 html_source_link
= v
.html_source_link
(mentity
.location
)
526 # FIXME less hideous hacks...
527 redef class DefinitionArticle
528 redef fun init_html_render
(v
, doc
, page
) do
529 var mentity
= self.mentity
530 if mentity
isa MProject or mentity
isa MModule then
531 var title
= new Template
532 title
.add mentity
.html_icon
533 title
.add mentity
.html_namespace
535 html_toc_title
= mentity
.html_name
536 if mentity
isa MModule then
537 html_source_link
= v
.html_source_link
(mentity
.location
)
539 else if mentity
isa MClassDef then
540 var title
= new Template
542 title
.add mentity
.mmodule
.html_namespace
543 html_title
= mentity
.html_declaration
544 html_subtitle
= title
545 html_toc_title
= "in {mentity.html_name}"
546 html_source_link
= v
.html_source_link
(mentity
.location
)
547 if page
isa MEntityPage and mentity
.is_intro
and mentity
.mmodule
!= page
.mentity
then
548 is_short_comment
= true
550 if page
isa MModulePage then is_toc_hidden
= true
551 else if mentity
isa MPropDef then
552 if page
isa MClassPage then
553 var title
= new Template
554 title
.add mentity
.html_icon
555 title
.add mentity
.html_declaration
557 html_subtitle
= mentity
.html_namespace
558 html_toc_title
= mentity
.html_name
560 var title
= new Template
562 title
.add mentity
.mclassdef
.html_link
564 html_toc_title
= "in {mentity.mclassdef.html_name}"
566 html_source_link
= v
.html_source_link
(mentity
.location
)
568 if page
isa MGroupPage and mentity
isa MModule then
575 redef class HomeArticle
576 redef fun init_html_render
(v
, doc
, page
) do
577 if v
.ctx
.opt_custom_title
.value
!= null then
578 self.html_title
= v
.ctx
.opt_custom_title
.value
.to_s
579 self.html_toc_title
= v
.ctx
.opt_custom_title
.value
.to_s
581 self.content
= v
.ctx
.opt_custom_intro
.value
586 redef class GraphArticle
587 redef fun init_html_render
(v
, doc
, page
) do
588 var output_dir
= v
.ctx
.output_dir
589 var path
= output_dir
/ graph_id
590 var path_sh
= path
.escape_to_sh
591 var file
= new FileWriter.open
("{path}.dot")
594 sys
.system
("\{ test -f {path_sh}.png && test -f {path_sh}.s.dot && diff -- {path_sh}.dot {path_sh}.s.dot >/dev/null 2>&1 ; \} || \{ cp -- {path_sh}.dot {path_sh}.s.dot && dot -Tpng -o{path_sh}.png -Tcmapx -o{path_sh}.map {path_sh}.s.dot ; \}")
595 var fmap
= new FileReader.open
("{path}.map")
596 self.map
= fmap
.read_all