this repo has no description
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
3/*
4 * Main authors:
5 * Guido Tack <guido.tack@monash.edu>
6 */
7
8/* This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11
12#include <minizinc/config.hh>
13#include <minizinc/copy.hh>
14#include <minizinc/eval_par.hh>
15#include <minizinc/htmlprinter.hh>
16#include <minizinc/model.hh>
17#include <minizinc/prettyprinter.hh>
18
19#include <cctype>
20#include <sstream>
21
22namespace MiniZinc {
23
24namespace HtmlDocOutput {
25
26// Trim leading space:
27// - always trim first line completely
28// - second line defines the base indentation
29std::string trim(const std::string& s0) {
30 std::string s = s0;
31 // remove carriage returns
32 size_t j = 0;
33 for (size_t i = 0; i < s.size(); i++) {
34 if (s[i] != '\r') s[j++] = s[i];
35 }
36 s.resize(j);
37 size_t first_line_indent = s.find_first_not_of(" \t");
38 if (first_line_indent == std::string::npos) return "";
39 size_t first_nl = s.find("\n");
40 std::ostringstream oss;
41 if (first_line_indent == first_nl) {
42 // first line is empty
43 oss << "\n";
44 std::cerr << "--empty first line\n";
45 } else {
46 // strip first line
47 size_t end_of_first_line =
48 first_nl == std::string::npos ? std::string::npos : first_nl - first_line_indent + 1;
49 oss << s.substr(first_line_indent, end_of_first_line);
50 std::cerr << "--" << first_line_indent << ", " << first_nl << "\n";
51 std::cerr << "'" << s.substr(first_line_indent, end_of_first_line) << "'\n";
52 }
53 if (first_nl == std::string::npos) return oss.str();
54 size_t unindent = s.find_first_not_of(" \t", first_nl + 1);
55 if (unindent == std::string::npos) return oss.str();
56 size_t pos = s.find("\n", first_nl + 1);
57 if (unindent == 0 || unindent > pos) {
58 oss << s.substr(first_nl + 1, std::string::npos);
59 return oss.str();
60 }
61 size_t lastpos = unindent;
62 while (pos != std::string::npos) {
63 oss << s.substr(lastpos, pos - lastpos) << "\n";
64 size_t next_indent = s.find_first_not_of(" \t", pos + 1);
65 if (next_indent == std::string::npos) {
66 lastpos = next_indent;
67 } else if (next_indent - (pos + 1) < unindent) {
68 lastpos = next_indent;
69 } else {
70 lastpos = pos + 1 + unindent;
71 }
72 pos = (lastpos == std::string::npos ? lastpos : s.find("\n", lastpos));
73 }
74 if (lastpos != std::string::npos) oss << s.substr(lastpos, std::string::npos);
75 return oss.str();
76}
77
78class DocItem {
79public:
80 enum DocType { T_PAR = 0, T_VAR = 1, T_FUN = 2 };
81 DocItem(const DocType& t0, std::string id0, std::string sig0, std::string doc0)
82 : t(t0), id(id0), sig(sig0), doc(doc0) {}
83 DocType t;
84 std::string id;
85 std::string sig;
86 std::string doc;
87};
88
89typedef std::unordered_map<FunctionI*, std::string> FunMap;
90
91class Group;
92
93class GroupMap {
94public:
95 typedef std::vector<Group*> Map;
96 Map m;
97 ~GroupMap();
98 Map::iterator find(const std::string& n);
99};
100
101class Group {
102public:
103 Group(const std::string& name0, const std::string& fullPath0)
104 : name(name0), fullPath(fullPath0) {}
105 std::string name;
106 std::string fullPath;
107 std::string desc;
108 std::string htmlName;
109 GroupMap subgroups;
110 std::vector<DocItem> items;
111
112 std::string getAnchor(int level, int indivFileLevel) {
113 if (level < indivFileLevel) {
114 return fullPath + ".html";
115 } else {
116 return "#" + fullPath;
117 }
118 }
119
120 std::string toHTML(int level, int indivFileLevel, Group* parent, int idx,
121 const std::string& basename, bool generateIndex) {
122 std::ostringstream oss;
123
124 int realLevel = (level < indivFileLevel) ? 0 : level - indivFileLevel;
125 oss << "<div class='mzn-group-level-" << realLevel << "'>\n";
126 if (parent) {
127 oss << "<div class='mzn-group-nav'>";
128 if (idx > 0) {
129 oss << "<a class='mzn-nav-prev' href='"
130 << parent->subgroups.m[idx - 1]->getAnchor(level - 1, indivFileLevel) << "' title='"
131 << parent->subgroups.m[idx - 1]->htmlName << "'>⇐</a> ";
132 }
133 oss << "<a class='mzn-nav-up' href='" << parent->getAnchor(level - 1, indivFileLevel)
134 << "' title='" << parent->htmlName << "'>⇧</a> ";
135 if (idx < parent->subgroups.m.size() - 1) {
136 oss << "<a class='mzn-nav-next' href='"
137 << parent->subgroups.m[idx + 1]->getAnchor(level - 1, indivFileLevel) << "' title='"
138 << parent->subgroups.m[idx + 1]->htmlName << "'>⇒</a> ";
139 }
140 if (generateIndex) oss << "<a href='doc-index.html'>Index</a>\n";
141 if (items.size() > 0) {
142 oss << "<a href='javascript:void(0)' onclick='revealAll()' class='mzn-nav-text'>reveal "
143 "all</a>\n";
144 oss << "<a href='javascript:void(0)' onclick='hideAll()' class='mzn-nav-text'>hide "
145 "all</a>\n";
146 }
147 oss << "</div>";
148 }
149 if (!htmlName.empty()) {
150 oss << "<div class='mzn-group-name'><a name='" << fullPath << "'>" << htmlName
151 << "</a></div>\n";
152 oss << "<div class='mzn-group-desc'>\n" << desc << "</div>\n";
153 }
154
155 if (subgroups.m.size() != 0) {
156 oss << "<p>Sections:</p>\n";
157 oss << "<ul>\n";
158 for (GroupMap::Map::iterator it = subgroups.m.begin(); it != subgroups.m.end(); ++it) {
159 oss << "<li><a href='" << (*it)->getAnchor(level, indivFileLevel) << "'>" << (*it)->htmlName
160 << "</a>\n";
161
162 if ((*it)->htmlName.empty()) {
163 std::cerr << "Warning: undocumented group " << (*it)->fullPath << "\n";
164 }
165 }
166 oss << "</ul>\n";
167 if (parent == NULL && generateIndex) {
168 oss << "<p><a href='doc-index.html'>Index</a></p>\n";
169 }
170 if (items.size() > 0) oss << "<p>Declarations in this section:</p>\n";
171 }
172
173 struct SortById {
174 bool operator()(const DocItem& i0, const DocItem& i1) {
175 return i0.t < i1.t || (i0.t == i1.t && i0.id < i1.id);
176 }
177 } _cmp;
178 std::stable_sort(items.begin(), items.end(), _cmp);
179
180 int cur_t = -1;
181 const char* dt[] = {"par", "var", "fun"};
182 const char* dt_desc[] = {"Parameters", "Variables", "Functions and Predicates"};
183 for (std::vector<DocItem>::const_iterator it = items.begin(); it != items.end(); ++it) {
184 if (it->t != cur_t) {
185 if (cur_t != -1) oss << "</div>\n";
186 cur_t = it->t;
187 oss << "<div class='mzn-decl-type-" << dt[cur_t] << "'>\n";
188 oss << "<div class='mzn-decl-type-heading'>" << dt_desc[cur_t] << "</div>\n";
189 }
190 oss << it->doc;
191 }
192 if (cur_t != -1) oss << "</div>\n";
193
194 if (level >= indivFileLevel) {
195 for (unsigned int i = 0; i < subgroups.m.size(); i++) {
196 oss << subgroups.m[i]->toHTML(level + 1, indivFileLevel, this, i, basename, generateIndex);
197 }
198 }
199
200 oss << "</div>";
201 return oss.str();
202 }
203
204 static std::string rstHeading(std::string s, int level) {
205 std::vector<char> levelChar({'#', '=', '-', '^', '+', '"'});
206 std::ostringstream oss;
207 oss << s << "\n";
208 for (int i = 0; i < s.size(); i++) oss << levelChar[level];
209 oss << "\n\n";
210 return oss.str();
211 }
212
213 std::string toRST(int level) {
214 std::ostringstream oss;
215 if (!htmlName.empty()) {
216 if (level == 0) {
217 oss << ".. _ch-lib-" << name << ":\n\n";
218 }
219 oss << rstHeading(htmlName, level);
220 oss << HtmlDocOutput::trim(desc) << "\n\n";
221 }
222 for (unsigned int i = 0; i < subgroups.m.size(); i++) {
223 oss << subgroups.m[i]->toRST(level + 1);
224 }
225 if (items.size() > 0) {
226 if (subgroups.m.size() != 0) {
227 oss << rstHeading("Other declarations", level + 1);
228 }
229 struct SortById {
230 bool operator()(const DocItem& i0, const DocItem& i1) {
231 return i0.t < i1.t || (i0.t == i1.t && i0.id < i1.id);
232 }
233 } _cmp;
234 std::stable_sort(items.begin(), items.end(), _cmp);
235
236 int cur_t = -1;
237 int nHeadings = 0;
238 for (std::vector<DocItem>::const_iterator it = items.begin(); it != items.end(); ++it) {
239 if (it->t != cur_t) {
240 cur_t = it->t;
241 nHeadings++;
242 }
243 }
244 cur_t = -1;
245 const char* dt_desc[] = {"Parameters", "Variables", "Functions and Predicates"};
246 for (std::vector<DocItem>::const_iterator it = items.begin(); it != items.end(); ++it) {
247 if (it->t != cur_t) {
248 cur_t = it->t;
249 if (nHeadings > 1)
250 oss << rstHeading(dt_desc[cur_t], subgroups.m.size() == 0 ? level + 1 : level + 2);
251 }
252 oss << it->doc;
253 }
254 }
255 return oss.str();
256 }
257};
258
259GroupMap::~GroupMap() {
260 for (Map::iterator it = m.begin(); it != m.end(); ++it) {
261 delete *it;
262 }
263}
264GroupMap::Map::iterator GroupMap::find(const std::string& n) {
265 for (Map::iterator it = m.begin(); it != m.end(); ++it)
266 if ((*it)->name == n) return it;
267 return m.end();
268}
269
270void addToGroup(Group& gm, const std::string& group, DocItem& di) {
271 std::vector<std::string> subgroups;
272 size_t lastpos = 0;
273 size_t pos = group.find(".");
274 while (pos != std::string::npos) {
275 subgroups.push_back(group.substr(lastpos, pos - lastpos));
276 lastpos = pos + 1;
277 pos = group.find(".", lastpos);
278 }
279 subgroups.push_back(group.substr(lastpos, std::string::npos));
280
281 GroupMap* cgm = &gm.subgroups;
282 std::string gpath(gm.fullPath);
283 for (unsigned int i = 0; i < subgroups.size(); i++) {
284 gpath += "-";
285 gpath += subgroups[i];
286 if (cgm->find(subgroups[i]) == cgm->m.end()) {
287 cgm->m.push_back(new Group(subgroups[i], gpath));
288 }
289 Group& g = **cgm->find(subgroups[i]);
290 if (i == subgroups.size() - 1) {
291 g.items.push_back(di);
292 } else {
293 cgm = &g.subgroups;
294 }
295 }
296}
297
298void setGroupDesc(Group& maingroup, const std::string& group, std::string htmlName, std::string s) {
299 if (group == "MAIN") {
300 if (!maingroup.htmlName.empty()) {
301 std::cerr << "Warning: two descriptions for group `" << group << "'\n";
302 }
303 maingroup.htmlName = htmlName;
304 maingroup.desc = s;
305 return;
306 }
307
308 std::vector<std::string> subgroups;
309 size_t lastpos = 0;
310 size_t pos = group.find(".");
311 while (pos != std::string::npos) {
312 subgroups.push_back(group.substr(lastpos, pos - lastpos));
313 lastpos = pos + 1;
314 pos = group.find(".", lastpos);
315 }
316 subgroups.push_back(group.substr(lastpos, std::string::npos));
317
318 GroupMap* cgm = &maingroup.subgroups;
319 std::string gpath(maingroup.fullPath);
320 for (unsigned int i = 0; i < subgroups.size(); i++) {
321 gpath += "-";
322 gpath += subgroups[i];
323 if (cgm->find(subgroups[i]) == cgm->m.end()) {
324 cgm->m.push_back(new Group(subgroups[i], gpath));
325 }
326 Group& g = **cgm->find(subgroups[i]);
327 if (i == subgroups.size() - 1) {
328 if (!g.htmlName.empty()) {
329 std::cerr << "Warning: two descriptions for group `" << group << "'\n";
330 }
331 g.htmlName = htmlName;
332 g.desc = s;
333 } else {
334 cgm = &g.subgroups;
335 }
336 }
337}
338
339std::string extractArgWord(std::string& s, size_t n) {
340 size_t start = n;
341 while (start < s.size() && s[start] != ' ' && s[start] != '\t') start++;
342 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) start++;
343 size_t end = start + 1;
344 while (end < s.size() && (isalnum(s[end]) || s[end] == '_' || s[end] == '.')) end++;
345 std::string ret = s.substr(start, end - start);
346 s = s.substr(end, std::string::npos);
347 return ret;
348}
349
350std::string makeHTMLId(const std::string& ident) {
351 std::ostringstream oss;
352 oss << "I";
353 bool prevWasSym = false;
354 for (size_t i = 0; i < ident.size(); i++) {
355 bool isSym = true;
356 switch (ident[i]) {
357 case '!':
358 oss << "-ex";
359 break;
360 case '=':
361 oss << "-eq";
362 break;
363 case '*':
364 oss << "-as";
365 break;
366 case '+':
367 oss << "-pl";
368 break;
369 case '-':
370 oss << "-mi";
371 break;
372 case '>':
373 oss << "-gr";
374 break;
375 case '<':
376 oss << "-lt";
377 break;
378 case '/':
379 oss << "-dv";
380 break;
381 case '\\':
382 oss << "-bs";
383 break;
384 case '~':
385 oss << "-tl";
386 break;
387 case '\'':
388 oss << "-tk";
389 break;
390 case ' ':
391 break;
392 case '\t':
393 break;
394 case '\n':
395 break;
396 case ':':
397 oss << "-cl";
398 break;
399 case '[':
400 oss << "-bo";
401 break;
402 case ']':
403 oss << "-bc";
404 break;
405 case '$':
406 oss << "-dd";
407 break;
408 case '(':
409 oss << "-po";
410 break;
411 case ')':
412 oss << "-pc";
413 break;
414 case ',':
415 oss << "-cm";
416 break;
417 default:
418 oss << (prevWasSym ? "-" : "") << ident[i];
419 isSym = false;
420 break;
421 }
422 prevWasSym = isSym;
423 }
424 return oss.str();
425}
426
427} // namespace HtmlDocOutput
428
429class CollectFunctionsVisitor : public ItemVisitor {
430protected:
431 EnvI& env;
432 HtmlDocOutput::FunMap& _funmap;
433 bool _includeStdLib;
434
435public:
436 CollectFunctionsVisitor(EnvI& env0, HtmlDocOutput::FunMap& funmap, bool includeStdLib)
437 : env(env0), _funmap(funmap), _includeStdLib(includeStdLib) {}
438 bool enterModel(Model* m) { return _includeStdLib || m->filename() != "stdlib.mzn"; }
439 void vFunctionI(FunctionI* fi) {
440 if (Call* docstring =
441 Expression::dyn_cast<Call>(getAnnotation(fi->ann(), constants().ann.doc_comment))) {
442 std::string ds = eval_string(env, docstring->arg(0));
443 std::string group("main");
444 size_t group_idx = ds.find("@group");
445 if (group_idx != std::string::npos) {
446 group = HtmlDocOutput::extractArgWord(ds, group_idx);
447 }
448 _funmap.insert(std::make_pair(fi, group));
449 }
450 }
451};
452
453class PrintHtmlVisitor : public ItemVisitor {
454protected:
455 EnvI& env;
456 HtmlDocOutput::Group& _maingroup;
457 HtmlDocOutput::FunMap& _funmap;
458 bool _includeStdLib;
459
460 std::vector<std::string> replaceArgs(std::string& s) {
461 std::vector<std::string> replacements;
462 std::ostringstream oss;
463 size_t lastpos = 0;
464 size_t pos = std::min(s.find("\\a"), s.find("\\p"));
465 size_t mathjax_open = s.find("\\(");
466 size_t mathjax_close = s.rfind("\\)");
467 if (pos == std::string::npos) return replacements;
468 while (pos != std::string::npos) {
469 oss << s.substr(lastpos, pos - lastpos);
470 size_t start = pos;
471 while (start < s.size() && s[start] != ' ' && s[start] != '\t') start++;
472 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) start++;
473 size_t end = start + 1;
474 while (end < s.size() && (isalnum(s[end]) || s[end] == '_')) end++;
475 if (s[pos + 1] == 'a') {
476 replacements.push_back(s.substr(start, end - start));
477 if (pos >= mathjax_open && pos <= mathjax_close) {
478 oss << "{\\bf " << replacements.back() << "}";
479 } else {
480 oss << "<span class='mzn-arg'>" << replacements.back() << "</span>";
481 }
482 } else {
483 if (pos >= mathjax_open && pos <= mathjax_close) {
484 oss << "{\\bf " << s.substr(start, end - start) << "}";
485 } else {
486 oss << "<span class='mzn-parm'>" << s.substr(start, end - start) << "</span>";
487 }
488 }
489 lastpos = end;
490 pos = std::min(s.find("\\a", lastpos), s.find("\\p", lastpos));
491 }
492 oss << s.substr(lastpos, std::string::npos);
493 s = oss.str();
494 return replacements;
495 }
496
497 std::pair<std::string, std::string> extractArgLine(std::string& s, size_t n) {
498 size_t start = n;
499 while (start < s.size() && s[start] != ' ' && s[start] != '\t') start++;
500 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) start++;
501 size_t end = start + 1;
502 while (end < s.size() && s[end] != ':') end++;
503 std::string arg = s.substr(start, end - start);
504 size_t doc_start = end + 1;
505 while (end < s.size() && s[end] != '\n') end++;
506 std::string ret = s.substr(doc_start, end - doc_start);
507 replaceArgs(ret);
508 s = s.substr(0, n) + s.substr(end, std::string::npos);
509 return make_pair(arg, ret);
510 }
511
512 std::string addHTML(const std::string& s) {
513 std::ostringstream oss;
514 size_t lastpos = 0;
515 size_t pos = s.find('\n');
516 bool inUl = false;
517 oss << "<p>\n";
518 while (pos != std::string::npos) {
519 oss << s.substr(lastpos, pos - lastpos);
520 size_t next = std::min(s.find('\n', pos + 1), s.find('-', pos + 1));
521 if (next == std::string::npos) {
522 lastpos = pos + 1;
523 break;
524 }
525 bool allwhite = true;
526 for (size_t cur = pos + 1; cur < next; cur++) {
527 if (s[cur] != ' ' && s[cur] != '\t') {
528 allwhite = false;
529 break;
530 }
531 }
532 if (allwhite) {
533 if (s[next] == '-') {
534 if (!inUl) {
535 oss << "<ul>\n";
536 inUl = true;
537 }
538 oss << "<li>";
539 } else {
540 if (inUl) {
541 oss << "</ul>\n";
542 inUl = false;
543 } else {
544 oss << "</p><p>\n";
545 }
546 }
547 lastpos = next + 1;
548 pos = s.find('\n', lastpos);
549 } else {
550 lastpos = pos + 1;
551 if (s[pos] == '\n') {
552 oss << " ";
553 }
554 if (s[next] == '-') {
555 pos = s.find('\n', next + 1);
556 } else {
557 pos = next;
558 }
559 }
560 }
561 oss << s.substr(lastpos, std::string::npos);
562 if (inUl) oss << "</ul>\n";
563 oss << "</p>\n";
564 return oss.str();
565 }
566
567public:
568 PrintHtmlVisitor(EnvI& env0, HtmlDocOutput::Group& mg, HtmlDocOutput::FunMap& fm,
569 bool includeStdLib)
570 : env(env0), _maingroup(mg), _funmap(fm), _includeStdLib(includeStdLib) {}
571 bool enterModel(Model* m) {
572 if (!_includeStdLib && m->filename() == "stdlib.mzn") return false;
573 const std::string& dc = m->docComment();
574 if (!dc.empty()) {
575 size_t gpos = dc.find("@groupdef");
576 while (gpos != std::string::npos) {
577 size_t start = gpos;
578 while (start < dc.size() && dc[start] != ' ' && dc[start] != '\t') start++;
579 while (start < dc.size() && (dc[start] == ' ' || dc[start] == '\t')) start++;
580 size_t end = start + 1;
581 while (end < dc.size() && (isalnum(dc[end]) || dc[end] == '_' || dc[end] == '.')) end++;
582 std::string groupName = dc.substr(start, end - start);
583 size_t doc_start = end + 1;
584 while (end < dc.size() && dc[end] != '\n') end++;
585 std::string groupHTMLName = dc.substr(doc_start, end - doc_start);
586
587 size_t next = dc.find("@groupdef", gpos + 1);
588 HtmlDocOutput::setGroupDesc(
589 _maingroup, groupName, groupHTMLName,
590 addHTML(dc.substr(end, next == std::string::npos ? next : next - end)));
591 gpos = next;
592 }
593 }
594 return true;
595 }
596 /// Visit variable declaration
597 void vVarDeclI(VarDeclI* vdi) {
598 if (Call* docstring = Expression::dyn_cast<Call>(
599 getAnnotation(vdi->e()->ann(), constants().ann.doc_comment))) {
600 std::string ds = eval_string(env, docstring->arg(0));
601 std::string group("main");
602 size_t group_idx = ds.find("@group");
603 if (group_idx != std::string::npos) {
604 group = HtmlDocOutput::extractArgWord(ds, group_idx);
605 }
606
607 std::ostringstream os;
608 std::string sig = vdi->e()->type().toString(env) + " " + vdi->e()->id()->str().str();
609 os << "<div class='mzn-vardecl' id='" << HtmlDocOutput::makeHTMLId(sig) << "'>\n";
610 os << "<div class='mzn-vardecl-code'>\n";
611 if (vdi->e()->ti()->type() == Type::ann()) {
612 os << "<span class='mzn-kw'>annotation</span> ";
613 os << "<span class='mzn-fn-id'>" << *vdi->e()->id() << "</span>";
614 } else {
615 os << *vdi->e()->ti() << ": " << *vdi->e()->id();
616 }
617 os << "</div><div class='mzn-vardecl-doc'>\n";
618 os << addHTML(ds);
619 os << "</div></div>";
620 GCLock lock;
621 HtmlDocOutput::DocItem di(
622 vdi->e()->type().ispar() ? HtmlDocOutput::DocItem::T_PAR : HtmlDocOutput::DocItem::T_VAR,
623 sig, sig, os.str());
624 HtmlDocOutput::addToGroup(_maingroup, group, di);
625 }
626 }
627 /// Visit function item
628 void vFunctionI(FunctionI* fi) {
629 if (Call* docstring =
630 Expression::dyn_cast<Call>(getAnnotation(fi->ann(), constants().ann.doc_comment))) {
631 std::string ds = eval_string(env, docstring->arg(0));
632 std::string group("main");
633 size_t group_idx = ds.find("@group");
634 if (group_idx != std::string::npos) {
635 group = HtmlDocOutput::extractArgWord(ds, group_idx);
636 }
637
638 size_t param_idx = ds.find("@param");
639 std::vector<std::pair<std::string, std::string> > params;
640 while (param_idx != std::string::npos) {
641 params.push_back(extractArgLine(ds, param_idx));
642 param_idx = ds.find("@param");
643 }
644
645 std::vector<std::string> args = replaceArgs(ds);
646
647 std::unordered_set<std::string> allArgs;
648 for (unsigned int i = 0; i < args.size(); i++) allArgs.insert(args[i]);
649 for (unsigned int i = 0; i < params.size(); i++) allArgs.insert(params[i].first);
650
651 GCLock lock;
652 for (unsigned int i = 0; i < fi->params().size(); i++) {
653 if (allArgs.find(fi->params()[i]->id()->str().str()) == allArgs.end()) {
654 std::cerr << "Warning: parameter " << *fi->params()[i]->id()
655 << " not documented for function " << fi->id() << " at location " << fi->loc()
656 << "\n";
657 }
658 }
659
660 std::string sig;
661 {
662 GCLock lock;
663 FunctionI* fi_c = new FunctionI(Location(), fi->id(), fi->ti(), fi->params());
664 std::ostringstream oss_sig;
665 oss_sig << *fi_c;
666 sig = oss_sig.str();
667 sig.resize(sig.size() - 2);
668 }
669
670 std::ostringstream os;
671 os << "<div class='mzn-fundecl' id='" << HtmlDocOutput::makeHTMLId(sig) << "'>\n";
672 os << "<div class='mzn-fundecl-code'>";
673 os << "<a href='javascript:void(0)' onclick='revealMore(this)' "
674 "class='mzn-fundecl-more'>◀</a>";
675
676 std::ostringstream fs;
677 if (fi->ti()->type() == Type::ann()) {
678 fs << "annotation ";
679 os << "<span class='mzn-kw'>annotation</span> ";
680 } else if (fi->ti()->type() == Type::parbool()) {
681 fs << "test ";
682 os << "<span class='mzn-kw'>test</span> ";
683 } else if (fi->ti()->type() == Type::varbool()) {
684 fs << "predicate ";
685 os << "<span class='mzn-kw'>predicate</span> ";
686 } else {
687 fs << "function " << *fi->ti() << ": ";
688 os << "<span class='mzn-kw'>function</span> <span class='mzn-ti'>" << *fi->ti()
689 << "</span>: ";
690 }
691 fs << fi->id() << "(";
692 os << "<span class='mzn-fn-id'>" << fi->id() << "</span>(";
693 size_t align = fs.str().size();
694 for (unsigned int i = 0; i < fi->params().size(); i++) {
695 fs << *fi->params()[i]->ti() << ": " << *fi->params()[i]->id();
696 if (i < fi->params().size() - 1) {
697 fs << ", ";
698 }
699 }
700 bool splitArgs = (fs.str().size() > 70);
701 for (unsigned int i = 0; i < fi->params().size(); i++) {
702 os << "<span class='mzn-ti'>" << *fi->params()[i]->ti() << "</span>: "
703 << "<span class='mzn-id'>" << *fi->params()[i]->id() << "</span>";
704 if (i < fi->params().size() - 1) {
705 os << ",";
706 if (splitArgs) {
707 os << "\n";
708 for (unsigned int j = static_cast<unsigned int>(align); j--;) os << " ";
709 } else {
710 os << " ";
711 }
712 }
713 }
714 os << ")";
715
716 if (fi->e()) {
717 FunctionI* f_body = fi;
718 bool alias;
719 do {
720 alias = false;
721 Call* c = Expression::dyn_cast<Call>(f_body->e());
722 if (c && c->n_args() == f_body->params().size()) {
723 bool sameParams = true;
724 for (unsigned int i = 0; i < f_body->params().size(); i++) {
725 Id* ident = c->arg(i)->dyn_cast<Id>();
726 if (ident == NULL || ident->decl() != f_body->params()[i] ||
727 ident->str() != c->decl()->params()[i]->id()->str()) {
728 sameParams = false;
729 break;
730 }
731 }
732 if (sameParams) {
733 alias = true;
734 f_body = c->decl();
735 }
736 }
737 } while (alias);
738 if (f_body->e()) {
739 std::ostringstream body_os;
740 Printer p(body_os, 70);
741 p.print(f_body->e());
742
743 std::string filename = f_body->loc().filename().str();
744 size_t lastSlash = filename.find_last_of("/");
745 if (lastSlash != std::string::npos) {
746 filename = filename.substr(lastSlash + 1, std::string::npos);
747 }
748 os << "<span class='mzn-fundecl-equals'> =</span>";
749 os << "\n<div class='mzn-fundecl-more-code'>";
750 os << "<div class='mzn-fundecl-body'>";
751 os << body_os.str();
752 os << "</div>\n";
753 os << "(standard decomposition from " << filename << ":" << f_body->loc().first_line()
754 << ")";
755 os << "</div>";
756 }
757 }
758
759 os << "</div>\n<div class='mzn-fundecl-doc'>\n";
760
761 if (fi->id().c_str()[0] == '\'') {
762 std::string op = fi->id().str();
763 op = op.substr(1, op.length() - 2);
764 const char* space = (op[0] >= 'a' ? " " : "");
765 if (fi->params().size() == 2) {
766 os << "<p>Usage: <span class=\"mzn-arg\">" << *fi->params()[0]->id() << space << op
767 << space << *fi->params()[1]->id() << "</span></p>";
768 } else if (fi->params().size() == 1) {
769 os << "<p>Usage: <span class=\"mzn-arg\">" << op << space << *fi->params()[0]->id()
770 << "</span></p>";
771 }
772 }
773
774 std::string dshtml = addHTML(ds);
775
776 os << dshtml;
777 if (params.size() > 0) {
778 os << "<div class='mzn-fundecl-params-heading'>Parameters</div>\n";
779 os << "<ul class='mzn-fundecl-params'>\n";
780 for (unsigned int i = 0; i < params.size(); i++) {
781 os << "<li><span class='mzn-arg'>" << params[i].first << "</span>: " << params[i].second
782 << "</li>\n";
783 }
784 os << "</ul>\n";
785 }
786 os << "</div>";
787 os << "</div>";
788
789 HtmlDocOutput::DocItem di(HtmlDocOutput::DocItem::T_FUN, fi->id().str(), sig, os.str());
790 HtmlDocOutput::addToGroup(_maingroup, group, di);
791 }
792 }
793};
794
795std::vector<HtmlDocument> HtmlPrinter::printHtml(EnvI& env, MiniZinc::Model* m,
796 const std::string& basename, int splitLevel,
797 bool includeStdLib, bool generateIndex) {
798 using namespace HtmlDocOutput;
799 Group g(basename, basename);
800 FunMap funMap;
801 CollectFunctionsVisitor fv(env, funMap, includeStdLib);
802 iterItems(fv, m);
803 PrintHtmlVisitor phv(env, g, funMap, includeStdLib);
804 iterItems(phv, m);
805
806 std::vector<HtmlDocument> ret;
807
808 struct SI {
809 Group* g;
810 Group* p;
811 int level;
812 int idx;
813 SI(Group* g0, Group* p0, int level0, int idx0) : g(g0), p(p0), level(level0), idx(idx0) {}
814 };
815
816 struct IndexEntry {
817 std::string id;
818 std::string sig;
819 std::string link;
820 std::string groupName;
821 IndexEntry(const std::string& id0, const std::string& sig0, const std::string& link0,
822 const std::string& groupName0)
823 : id(id0), sig(sig0), link(link0), groupName(groupName0) {
824 size_t spacepos = id.find_last_of(' ');
825 if (spacepos != std::string::npos) {
826 id = id.substr(spacepos + 1);
827 }
828 }
829 bool operator<(const IndexEntry& e) const {
830 if (!isalpha(id[0]) && isalpha(e.id[0])) return true;
831 return id == e.id ? groupName < e.groupName : id < e.id;
832 }
833 };
834 std::vector<IndexEntry> index;
835
836 std::vector<SI> stack;
837 stack.push_back(SI(&g, NULL, 0, 0));
838 while (!stack.empty()) {
839 Group& g = *stack.back().g;
840 int curLevel = stack.back().level;
841 int curIdx = stack.back().idx;
842 Group* p = stack.back().p;
843 stack.pop_back();
844 for (auto it : g.items) {
845 index.push_back(IndexEntry(it.id, it.sig, g.fullPath, g.htmlName));
846 }
847 ret.push_back(HtmlDocument(g.fullPath, g.htmlName,
848 g.toHTML(curLevel, splitLevel, p, curIdx, basename, generateIndex)));
849 if (curLevel < splitLevel) {
850 for (unsigned int i = 0; i < g.subgroups.m.size(); i++) {
851 stack.push_back(SI(g.subgroups.m[i], &g, curLevel + 1, i));
852 }
853 }
854 }
855
856 if (generateIndex) {
857 std::sort(index.begin(), index.end());
858 std::ostringstream oss;
859 index.push_back(IndexEntry("", "", "", ""));
860
861 std::vector<std::string> idxSections;
862
863 if (index.size() != 0) {
864 if (isalpha(index[0].id[0])) {
865 char idxSec_c = (char)toupper(index[0].id[0]);
866 std::string idxSec(&idxSec_c, 1);
867 oss << "<h3 id='Idx" << idxSec << "'>" << idxSec << "</h3>\n";
868 idxSections.push_back(idxSec);
869 } else {
870 oss << "<h3 id='IdxSymbols'>Symbols</h3>\n";
871 idxSections.push_back("Symbols");
872 }
873 }
874 oss << "<ul>\n";
875 std::string prevId = index.size() == 0 ? "" : index[0].id;
876 std::vector<IndexEntry> curEntries;
877 for (auto ie : index) {
878 if (ie.id != prevId) {
879 oss << "<li>";
880 assert(curEntries.size() != 0);
881 IndexEntry& cur = curEntries[0];
882 if (curEntries.size() == 1) {
883 oss << cur.id << " <a href='" << cur.link << ".html#"
884 << HtmlDocOutput::makeHTMLId(cur.sig) << "'>"
885 << "(" << cur.groupName << ")</a>";
886 } else {
887 oss << cur.id << " (";
888 bool first = true;
889 for (auto i_ie : curEntries) {
890 if (first) {
891 first = false;
892 } else {
893 oss << ", ";
894 }
895 oss << "<a href='" << i_ie.link << ".html#" << HtmlDocOutput::makeHTMLId(i_ie.sig)
896 << "'>";
897 oss << i_ie.groupName << "</a>";
898 }
899 oss << ")";
900 }
901 oss << "</li>\n";
902 curEntries.clear();
903 }
904 if (isalpha(ie.id[0]) && ie.id[0] != prevId[0]) {
905 char idxSec_c = (char)toupper(ie.id[0]);
906 std::string idxSec(&idxSec_c, 1);
907 oss << "</ul>\n<h3 id='Idx" << idxSec << "'>" << idxSec << "</h3><ul>";
908 idxSections.push_back(idxSec);
909 }
910 prevId = ie.id;
911 if (curEntries.size() == 0 || curEntries.back().groupName != ie.groupName) {
912 curEntries.push_back(ie);
913 }
914 }
915 oss << "</ul>\n";
916
917 std::ostringstream oss_header;
918 oss_header << "<div class='mzn-group-level-0'>\n";
919 oss_header << "<div class='mzn-group-nav'>";
920 oss_header << "<a class='mzn-nav-up' href='" << g.getAnchor(0, 1) << "' title='" << g.htmlName
921 << "'>⇧</a> ";
922 bool first = true;
923 for (auto is : idxSections) {
924 if (first) {
925 first = false;
926 } else {
927 oss_header << " | ";
928 }
929 oss_header << "<a href='#Idx" << is << "'>" << is << "</a>";
930 }
931
932 oss_header << "</div>";
933
934 oss_header << "<div class='mzn-group-name'>Index</div>\n";
935
936 HtmlDocument idx("doc-index", "Index", oss_header.str() + oss.str());
937 ret.push_back(idx);
938 }
939 return ret;
940}
941
942class PrintRSTVisitor : public ItemVisitor {
943protected:
944 EnvI& env;
945 HtmlDocOutput::Group& _maingroup;
946 HtmlDocOutput::FunMap& _funmap;
947 bool _includeStdLib;
948
949 std::vector<std::string> replaceArgsRST(std::string& s) {
950 std::vector<std::string> replacements;
951 std::ostringstream oss;
952 size_t lastpos = 0;
953 size_t pos = std::min(s.find("\\a"), s.find("\\p"));
954 size_t mathjax_open = s.find("\\(");
955 size_t mathjax_close = s.rfind("\\)");
956 if (pos == std::string::npos) return replacements;
957 while (pos != std::string::npos) {
958 oss << s.substr(lastpos, pos - lastpos);
959 size_t start = pos;
960 while (start < s.size() && s[start] != ' ' && s[start] != '\t') start++;
961 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) start++;
962 size_t end = start + 1;
963 while (end < s.size() && (isalnum(s[end]) || s[end] == '_')) end++;
964 bool needSpace = pos != 0 && s[pos - 1] != ' ' && s[pos - 1] != '\n';
965 if (s[pos + 1] == 'a') {
966 replacements.push_back(s.substr(start, end - start));
967 if (pos >= mathjax_open && pos <= mathjax_close) {
968 oss << "{\\bf " << replacements.back() << "}";
969 } else {
970 oss << (needSpace ? " " : "") << "``" << replacements.back() << "`` ";
971 }
972 } else {
973 if (pos >= mathjax_open && pos <= mathjax_close) {
974 oss << "{\\bf " << s.substr(start, end - start) << "}";
975 } else {
976 oss << (needSpace ? " " : "") << "``" << s.substr(start, end - start) << "`` ";
977 }
978 }
979 lastpos = end;
980 pos = std::min(s.find("\\a", lastpos), s.find("\\p", lastpos));
981 }
982 oss << s.substr(lastpos, std::string::npos);
983 s = oss.str();
984
985 std::ostringstream oss2;
986 pos = std::min(s.find("\\("), s.find("\\)"));
987 lastpos = 0;
988 while (pos != std::string::npos) {
989 if (s[pos + 1] == ')') {
990 // remove trailing whitespace
991 std::string t = s.substr(lastpos, pos - lastpos);
992 size_t t_end = t.find_last_not_of(" ");
993 if (t_end != std::string::npos) t_end++;
994 oss2 << t.substr(0, t_end);
995 } else {
996 oss2 << s.substr(lastpos, pos - lastpos);
997 }
998 lastpos = pos + 2;
999 if (s[pos + 1] == '(') {
1000 oss2 << ":math:`";
1001 lastpos = s.find_first_not_of(" ", lastpos);
1002 } else {
1003 oss2 << "`";
1004 }
1005 pos = std::min(s.find("\\(", lastpos), s.find("\\)", lastpos));
1006 }
1007 oss2 << s.substr(lastpos, std::string::npos);
1008 s = oss2.str();
1009 return replacements;
1010 }
1011
1012 std::pair<std::string, std::string> extractArgLine(std::string& s, size_t n) {
1013 size_t start = n;
1014 while (start < s.size() && s[start] != ' ' && s[start] != '\t') start++;
1015 while (start < s.size() && (s[start] == ' ' || s[start] == '\t')) start++;
1016 size_t end = start + 1;
1017 while (end < s.size() && s[end] != ':') end++;
1018 std::string arg = s.substr(start, end - start);
1019 size_t doc_start = end + 1;
1020 while (end < s.size() && s[end] != '\n') end++;
1021 std::string ret = s.substr(doc_start, end - doc_start);
1022 replaceArgsRST(ret);
1023 s = s.substr(0, n) + s.substr(end, std::string::npos);
1024 return make_pair(arg, ret);
1025 }
1026
1027public:
1028 PrintRSTVisitor(EnvI& env0, HtmlDocOutput::Group& mg, HtmlDocOutput::FunMap& fm,
1029 bool includeStdLib)
1030 : env(env0), _maingroup(mg), _funmap(fm), _includeStdLib(includeStdLib) {}
1031 bool enterModel(Model* m) {
1032 if (!_includeStdLib && m->filename() == "stdlib.mzn") return false;
1033 const std::string& dc = m->docComment();
1034 if (!dc.empty()) {
1035 size_t gpos = dc.find("@groupdef");
1036 while (gpos != std::string::npos) {
1037 size_t start = gpos;
1038 while (start < dc.size() && dc[start] != ' ' && dc[start] != '\t') start++;
1039 while (start < dc.size() && (dc[start] == ' ' || dc[start] == '\t')) start++;
1040 size_t end = start + 1;
1041 while (end < dc.size() && (isalnum(dc[end]) || dc[end] == '_' || dc[end] == '.')) end++;
1042 std::string groupName = dc.substr(start, end - start);
1043 size_t doc_start = end + 1;
1044 while (end < dc.size() && dc[end] != '\n') end++;
1045 std::string groupHTMLName = dc.substr(doc_start, end - doc_start);
1046
1047 size_t next = dc.find("@groupdef", gpos + 1);
1048 HtmlDocOutput::setGroupDesc(
1049 _maingroup, groupName, groupHTMLName,
1050 (dc.substr(end, next == std::string::npos ? next : next - end)));
1051 gpos = next;
1052 }
1053 }
1054 return true;
1055 }
1056 /// Visit variable declaration
1057 void vVarDeclI(VarDeclI* vdi) {
1058 if (Call* docstring = Expression::dyn_cast<Call>(
1059 getAnnotation(vdi->e()->ann(), constants().ann.doc_comment))) {
1060 std::string ds = eval_string(env, docstring->arg(0));
1061 std::string group("main");
1062 size_t group_idx = ds.find("@group");
1063 if (group_idx != std::string::npos) {
1064 group = HtmlDocOutput::extractArgWord(ds, group_idx);
1065 }
1066 std::ostringstream os;
1067 std::string sig = vdi->e()->type().toString(env) + " " + vdi->e()->id()->str().str();
1068
1069 std::string myMainGroup = group.substr(0, group.find_first_of("."));
1070 auto it = _maingroup.subgroups.find(myMainGroup);
1071 os << ".. index::\n";
1072 if (it != _maingroup.subgroups.m.end()) {
1073 os << " pair: " << (*it)->htmlName << "; " << *vdi->e()->id() << "\n\n";
1074 } else {
1075 std::cerr << "did not find " << myMainGroup << "\n";
1076 os << " single: " << *vdi->e()->id() << "\n\n";
1077 }
1078
1079 os << ".. code-block:: minizinc\n\n";
1080 if (vdi->e()->ti()->type() == Type::ann()) {
1081 os << " annotation " << *vdi->e()->id();
1082 } else {
1083 os << " " << *vdi->e()->ti() << ": " << *vdi->e()->id();
1084 }
1085 os << "\n\n";
1086 os << HtmlDocOutput::trim(ds) << "\n\n";
1087 GCLock lock;
1088 HtmlDocOutput::DocItem di(
1089 vdi->e()->type().ispar() ? HtmlDocOutput::DocItem::T_PAR : HtmlDocOutput::DocItem::T_VAR,
1090 sig, sig, os.str());
1091 HtmlDocOutput::addToGroup(_maingroup, group, di);
1092 }
1093 }
1094 /// Visit function item
1095 void vFunctionI(FunctionI* fi) {
1096 if (Call* docstring =
1097 Expression::dyn_cast<Call>(getAnnotation(fi->ann(), constants().ann.doc_comment))) {
1098 std::string ds = eval_string(env, docstring->arg(0));
1099 std::string group("main");
1100 size_t group_idx = ds.find("@group");
1101 if (group_idx != std::string::npos) {
1102 group = HtmlDocOutput::extractArgWord(ds, group_idx);
1103 }
1104
1105 size_t param_idx = ds.find("@param");
1106 std::vector<std::pair<std::string, std::string> > params;
1107 while (param_idx != std::string::npos) {
1108 params.push_back(extractArgLine(ds, param_idx));
1109 param_idx = ds.find("@param");
1110 }
1111
1112 std::vector<std::string> args = replaceArgsRST(ds);
1113
1114 std::unordered_set<std::string> allArgs;
1115 for (unsigned int i = 0; i < args.size(); i++) allArgs.insert(args[i]);
1116 for (unsigned int i = 0; i < params.size(); i++) allArgs.insert(params[i].first);
1117
1118 GCLock lock;
1119 for (unsigned int i = 0; i < fi->params().size(); i++) {
1120 if (allArgs.find(fi->params()[i]->id()->str().str()) == allArgs.end()) {
1121 std::cerr << "Warning: parameter " << *fi->params()[i]->id()
1122 << " not documented for function " << fi->id() << " at location " << fi->loc()
1123 << "\n";
1124 }
1125 }
1126
1127 std::string sig;
1128 {
1129 GCLock lock;
1130 FunctionI* fi_c = new FunctionI(Location(), fi->id(), fi->ti(), fi->params());
1131 std::ostringstream oss_sig;
1132 oss_sig << *fi_c;
1133 sig = oss_sig.str();
1134 sig.resize(sig.size() - 2);
1135 }
1136
1137 std::ostringstream os;
1138 std::ostringstream fs;
1139 std::string myMainGroup = group.substr(0, group.find_first_of("."));
1140 auto it = _maingroup.subgroups.find(myMainGroup);
1141 os << ".. index::\n";
1142 if (it != _maingroup.subgroups.m.end()) {
1143 os << " pair: " << (*it)->htmlName << "; " << fi->id() << "\n\n";
1144 } else {
1145 std::cerr << "did not find " << myMainGroup << "\n";
1146 os << " single: " << fi->id() << "\n\n";
1147 }
1148 os << ".. code-block:: minizinc\n\n";
1149
1150 if (fi->ti()->type() == Type::ann()) {
1151 fs << "annotation ";
1152 } else if (fi->ti()->type() == Type::parbool()) {
1153 fs << "test ";
1154 } else if (fi->ti()->type() == Type::varbool()) {
1155 fs << "predicate ";
1156 } else {
1157 fs << "function " << *fi->ti() << ": ";
1158 }
1159 fs << fi->id() << "(";
1160 os << " " << fs.str();
1161 size_t align = fs.str().size();
1162 for (unsigned int i = 0; i < fi->params().size(); i++) {
1163 fs << *fi->params()[i]->ti();
1164 std::ostringstream fid;
1165 fid << *fi->params()[i]->id();
1166 if (fid.str().size() != 0) fs << ": " << *fi->params()[i]->id();
1167 if (i < fi->params().size() - 1) {
1168 fs << ", ";
1169 }
1170 }
1171 bool splitArgs = (fs.str().size() > 70);
1172 for (unsigned int i = 0; i < fi->params().size(); i++) {
1173 os << *fi->params()[i]->ti();
1174 std::ostringstream fid;
1175 fid << *fi->params()[i]->id();
1176 if (fid.str().size() != 0) os << ": " << *fi->params()[i]->id();
1177 if (i < fi->params().size() - 1) {
1178 os << ",";
1179 if (splitArgs) {
1180 os << "\n ";
1181 for (unsigned int j = static_cast<unsigned int>(align); j--;) os << " ";
1182 } else {
1183 os << " ";
1184 }
1185 }
1186 }
1187 os << ")";
1188
1189 os << "\n\n";
1190
1191 if (fi->id().c_str()[0] == '\'') {
1192 std::string op = fi->id().str();
1193 op = op.substr(1, op.length() - 2);
1194 if (fi->params().size() == 2) {
1195 os << "Usage: ``" << *fi->params()[0]->id() << " " << op << " " << *fi->params()[1]->id()
1196 << "``\n\n";
1197 } else if (fi->params().size() == 1) {
1198 os << "Usage: ``" << op << " " << *fi->params()[0]->id() << "``\n\n";
1199 }
1200 }
1201
1202 os << HtmlDocOutput::trim(ds) << "\n\n";
1203
1204 if (fi->e()) {
1205 FunctionI* f_body = fi;
1206 bool alias;
1207 do {
1208 alias = false;
1209 Call* c = Expression::dyn_cast<Call>(f_body->e());
1210 if (c && c->n_args() == f_body->params().size()) {
1211 bool sameParams = true;
1212 for (unsigned int i = 0; i < f_body->params().size(); i++) {
1213 Id* ident = c->arg(i)->dyn_cast<Id>();
1214 if (ident == NULL || ident->decl() != f_body->params()[i] ||
1215 ident->str() != c->decl()->params()[i]->id()->str()) {
1216 sameParams = false;
1217 break;
1218 }
1219 }
1220 if (sameParams) {
1221 alias = true;
1222 f_body = c->decl();
1223 }
1224 }
1225 } while (alias);
1226 if (f_body->e()) {
1227 std::string filename = f_body->loc().filename().str();
1228 size_t filePos = filename.find("std/");
1229 if (filePos != std::string::npos) {
1230 filePos += 4;
1231 os << ".. only:: builder_html\n\n";
1232 os << " `More... <https://github.com/MiniZinc/libminizinc/blob/" << MZN_VERSION_MAJOR
1233 << "." << MZN_VERSION_MINOR << "." << MZN_VERSION_PATCH << "/share/minizinc/std/"
1234 << filename.substr(filePos, std::string::npos) << "#L" << f_body->loc().first_line()
1235 << "-L" << f_body->loc().last_line() << ">`__\n\n";
1236 }
1237 }
1238 }
1239
1240 if (params.size() > 0) {
1241 os << "Parameters:\n\n";
1242 for (unsigned int i = 0; i < params.size(); i++) {
1243 os << "- ``" << params[i].first << "``: " << params[i].second << "\n";
1244 }
1245 os << "\n";
1246 }
1247 os << "\n";
1248
1249 HtmlDocOutput::DocItem di(HtmlDocOutput::DocItem::T_FUN, fi->id().str(), sig, os.str());
1250 HtmlDocOutput::addToGroup(_maingroup, group, di);
1251 }
1252 }
1253};
1254
1255std::vector<HtmlDocument> RSTPrinter::printRST(EnvI& env, MiniZinc::Model* m,
1256 const std::string& basename, int splitLevel,
1257 bool includeStdLib, bool generateIndex) {
1258 using namespace HtmlDocOutput;
1259 Group g(basename, basename);
1260 FunMap funMap;
1261 CollectFunctionsVisitor fv(env, funMap, includeStdLib);
1262 iterItems(fv, m);
1263 PrintRSTVisitor prv(env, g, funMap, includeStdLib);
1264 iterItems(prv, m);
1265
1266 std::vector<HtmlDocument> ret;
1267
1268 std::ostringstream oss;
1269 oss << Group::rstHeading(g.htmlName, 0);
1270 oss << trim(g.desc) << "\n";
1271 oss << ".. toctree::\n\n";
1272 for (auto sg : g.subgroups.m) {
1273 oss << " " << sg->fullPath << "\n";
1274 }
1275
1276 ret.push_back(HtmlDocument(g.fullPath, g.htmlName, oss.str()));
1277
1278 for (auto& sg : g.subgroups.m) {
1279 ret.push_back(HtmlDocument(sg->fullPath, sg->htmlName, sg->toRST(0)));
1280 }
1281 return ret;
1282}
1283
1284} // namespace MiniZinc