Line data Source code
1 : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 : Copyright (c) 2019-2023 The plumed team
3 : (see the PEOPLE file at the root of the distribution for a list of names)
4 :
5 : See http://www.plumed.org for more information.
6 :
7 : This file is part of plumed, version 2.
8 :
9 : plumed is free software: you can redistribute it and/or modify
10 : it under the terms of the GNU Lesser General Public License as published by
11 : the Free Software Foundation, either version 3 of the License, or
12 : (at your option) any later version.
13 :
14 : plumed is distributed in the hope that it will be useful,
15 : but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : GNU Lesser General Public License for more details.
18 :
19 : You should have received a copy of the GNU Lesser General Public License
20 : along with plumed. If not, see <http://www.gnu.org/licenses/>.
21 : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
22 : #include "CLTool.h"
23 : #include "core/CLToolRegister.h"
24 : #include "tools/Tools.h"
25 : #include "config/Config.h"
26 : #include "core/ActionRegister.h"
27 : #include "core/ActionWithValue.h"
28 : #include "core/ActionWithVirtualAtom.h"
29 : #include "core/ActionShortcut.h"
30 : #include "core/ActionSet.h"
31 : #include "core/PlumedMain.h"
32 : #include "tools/Communicator.h"
33 : #include "tools/IFile.h"
34 : #include <cstdio>
35 : #include <string>
36 : #include <vector>
37 : #include <iostream>
38 : #include <fstream>
39 :
40 : namespace PLMD {
41 : namespace cltools {
42 :
43 : //+PLUMEDOC TOOLS gen_example
44 : /*
45 : gen_example is a tool that you can use to construct an example for the manual that users can interact with to understand
46 :
47 : The example constructed by this action is in html. In all probability you will never need to use this
48 : tool. However, it is used within the scripts that generate the html manual for PLUMED. If you need to use this
49 : tool outside those scripts the input is specified using the following command line arguments.
50 :
51 : THIS COMMAND WILL LIKELY BE DELETED SOON AS IT HAS BEEN SUPERCEDED BY PLUMEDTOHTML.
52 :
53 : ## Examples
54 :
55 : The following generates an example based on the contents of the plumed file plumed.dat
56 :
57 : ```plumed
58 : plumed gen_example --plumed plumed.dat --status working
59 : ```
60 :
61 :
62 : */
63 : //+ENDPLUMEDOC
64 :
65 : class GenExample:
66 : public CLTool {
67 : private:
68 : int multi;
69 : std::string status, version;
70 : Communicator intracomm;
71 : Communicator intercomm;
72 : public:
73 : static void registerKeywords( Keywords& keys );
74 : explicit GenExample(const CLToolOptions& co );
75 : int main(FILE* in, FILE*out,Communicator& pc) override;
76 7 : std::string description()const override {
77 7 : return "construct an example for the manual that users can interact with";
78 : }
79 : void printExampleInput( const std::vector<std::vector<std::string> >& input, const std::string& egname, const std::string& divname, std::ofstream& ofile );
80 : std::vector<std::vector<std::string> > createLongInput( const std::vector<std::vector<std::string> >& input );
81 : };
82 :
83 21972 : PLUMED_REGISTER_CLTOOL(GenExample,"gen_example")
84 :
85 7321 : void GenExample::registerKeywords( Keywords& keys ) {
86 7321 : CLTool::registerKeywords( keys );
87 7321 : keys.add("compulsory","--plumed","plumed.dat","convert the input in this file to the html manual");
88 7321 : keys.add("compulsory","--out","example.html","the file on which to output the example in html");
89 7321 : keys.add("compulsory","--name","ppp","the name to use for this particular input");
90 7321 : keys.add("compulsory","--status","nobadge","whether or not the input file works");
91 7321 : keys.add("compulsory","--multi","0","set number of replicas for multi environment (needs MPI)");
92 7321 : }
93 :
94 9 : GenExample::GenExample(const CLToolOptions& co ):
95 : CLTool(co),
96 9 : multi(0),
97 18 : status("nobadge"),
98 9 : version("master") {
99 9 : inputdata=inputType::commandline;
100 9 : }
101 :
102 2 : int GenExample::main(FILE* in, FILE*out,Communicator& pc) {
103 :
104 : // set up for multi replica driver:
105 2 : parse("--multi",multi);
106 2 : if(multi) {
107 0 : int ntot=pc.Get_size();
108 0 : int nintra=ntot/multi;
109 0 : if(multi*nintra!=ntot) {
110 0 : error("invalid number of processes for multi environment");
111 : }
112 0 : pc.Split(pc.Get_rank()/nintra,pc.Get_rank(),intracomm);
113 0 : pc.Split(pc.Get_rank()%nintra,pc.Get_rank(),intercomm);
114 : } else {
115 2 : intracomm.Set_comm(pc.Get_comm());
116 : }
117 :
118 4 : if( config::getVersionLong().find("dev")==std::string::npos ) {
119 0 : version="v"+config::getVersion();
120 : }
121 : std::string fname, egname, outfile;
122 2 : parse("--plumed",fname);
123 2 : parse("--name",egname);
124 2 : parse("--out",outfile);
125 2 : parse("--status",status);
126 :
127 2 : int r=0;
128 2 : if(intracomm.Get_rank()==0) {
129 2 : r=intercomm.Get_rank();
130 : }
131 2 : intracomm.Bcast(r,0);
132 2 : if(r>0) {
133 : outfile="/dev/null";
134 : }
135 :
136 2 : IFile ifile;
137 2 : ifile.open(fname);
138 2 : ifile.allowNoEOL();
139 2 : std::ofstream ofile;
140 2 : ofile.open(outfile);
141 2 : std::vector<bool> shortcuts;
142 : bool hasshortcuts=false, endplumed=false;
143 2 : std::vector<std::vector<std::string> > input;
144 2 : std::vector<std::string> words;
145 20 : while( Tools::getParsedLine(ifile, words, false) ) {
146 18 : input.push_back( words );
147 18 : shortcuts.push_back( false );
148 18 : if( words.empty() || words[0].find("#")!=std::string::npos || endplumed ) {
149 6 : continue;
150 : }
151 14 : std::vector<std::string> interpreted( words );
152 14 : Tools::interpretLabel(interpreted);
153 14 : if( interpreted[0]=="ENDPLUMED" ) {
154 : endplumed=true;
155 : continue;
156 : }
157 12 : Keywords keys;
158 12 : actionRegister().getKeywords( interpreted[0], keys );
159 12 : if( status=="working" && keys.exists("IS_SHORTCUT") ) {
160 2 : hasshortcuts=shortcuts[shortcuts.size()-1]=true;
161 : }
162 14 : }
163 2 : ifile.close();
164 2 : if( hasshortcuts ) {
165 2 : ofile<<"<div style=\"width: 80%; float:left\" id=\"value_details_"<<egname<<"\"> Click on the labels of the actions for more information on what each action computes </div>\n";
166 3 : ofile<<"<div style=\"width: 10%; float:left\"><button type=\"button\" id=\""<<egname<<"_button\" onclick=\'swapInput(\""<<egname<<"\")\'>contract shortcuts</button></div>";
167 : } else {
168 2 : ofile<<"<div style=\"width: 90%; float:left\" id=\"value_details_"<<egname<<"\"> Click on the labels of the actions for more information on what each action computes </div>\n";
169 : }
170 2 : ofile<<"<div style=\"width: 10%; float:left\">";
171 2 : ofile<<"<img src=\"https://img.shields.io/badge/";
172 2 : if(status=="working") {
173 1 : ofile<<version<<"-passing-green";
174 1 : } else if(status=="broken") {
175 0 : ofile<<version<<"-failed-red";
176 1 : } else if(status=="loads") {
177 0 : ofile<<"with-LOAD-yellow";
178 1 : } else if(status=="incomplete") {
179 0 : ofile<<version<<"-incomplete-yellow";
180 1 : } else if ( status=="nobadge" ) {
181 1 : ofile<<"nobadge-36454F";
182 : } else {
183 0 : error("unknown status: "+status);
184 : }
185 4 : ofile<<".svg\" alt=\"tested on "<<version<<"\" /></div>";
186 2 : ofile.flush();
187 2 : if( hasshortcuts ) {
188 : // Write out the short version of the input
189 2 : ofile<<"<div style=\"width: 100%; float:left\" id=\"input_"<<egname<<"\"></div>"<<std::endl;
190 : // Write an extra pre to make sure the html after the example is put in the right place on the page
191 1 : ofile<<"<pre style=\"width: 97%;\" class=\"fragment\"></pre>"<<std::endl;
192 1 : ofile<<"<script type=\"text/javascript\">"<<std::endl;
193 1 : ofile<<"if (window.addEventListener) { // Mozilla, Netscape, Firefox"<<std::endl;
194 2 : ofile<<" window.addEventListener('load', "<<egname<<"Load, false);"<<std::endl;
195 1 : ofile<<"} else if (window.attachEvent) { // IE"<<std::endl;
196 2 : ofile<<" window.attachEvent('onload', "<<egname<<"Load);"<<std::endl;
197 1 : ofile<<"}"<<std::endl;
198 2 : ofile<<"function "<<egname<<"Load(event) {"<<std::endl;
199 2 : ofile<<" swapInput(\""<<egname<<"\");"<<std::endl;
200 1 : ofile<<"}"<<std::endl;
201 1 : ofile<<"</script>"<<std::endl;
202 2 : ofile<<"<div style=\"display:none;\" id=\""<<egname<<"short\">"<<std::endl;
203 1 : printExampleInput( input, egname + "short", egname, ofile );
204 1 : ofile<<"</div>"<<std::endl;
205 : // Write out long version of the input
206 2 : ofile<<"<div style=\"display:none;\" id=\""<<egname<<"long\">";
207 1 : std::vector<std::vector<std::string> > long_input = createLongInput( input );
208 1 : printExampleInput( long_input, egname + "long", egname, ofile );
209 1 : ofile<<"</div>"<<std::endl;
210 1 : } else {
211 1 : printExampleInput( input, egname, egname, ofile );
212 : }
213 2 : ofile.close();
214 2 : return 0;
215 4 : }
216 :
217 1 : std::vector<std::vector<std::string> > GenExample::createLongInput( const std::vector<std::vector<std::string> >& input ) {
218 1 : std::vector<std::vector<std::string> > long_input;
219 1 : PlumedMain myplumed;
220 1 : int rr=sizeof(double), natoms=10000000;
221 1 : double kt=2.49;
222 1 : myplumed.cmd("setRealPrecision",&rr);
223 1 : if(Communicator::initialized()) {
224 0 : if(multi) {
225 0 : if(intracomm.Get_rank()==0) {
226 0 : myplumed.cmd("GREX setMPIIntercomm",&intercomm.Get_comm());
227 : }
228 0 : myplumed.cmd("GREX setMPIIntracomm",&intracomm.Get_comm());
229 0 : myplumed.cmd("GREX init");
230 : }
231 0 : myplumed.cmd("setMPIComm",&intracomm.Get_comm());
232 : }
233 : bool endplumed=false;
234 1 : myplumed.cmd("setNatoms",&natoms);
235 1 : myplumed.cmd("setKbT",&kt);
236 1 : myplumed.cmd("init");
237 10 : for(unsigned ll=0; ll<input.size(); ++ll) {
238 9 : if( input[ll].empty() || endplumed ) {
239 1 : long_input.push_back( input[ll] );
240 3 : continue;
241 : }
242 8 : if( input[ll][0].find("#")!=std::string::npos ) {
243 1 : long_input.push_back( input[ll] );
244 1 : continue;
245 : }
246 7 : std::vector<std::string> interpreted( input[ll] );
247 7 : Tools::interpretLabel(interpreted);
248 7 : if( interpreted[0]=="ENDPLUMED" ) {
249 : endplumed=true;
250 1 : long_input.push_back( input[ll] );
251 : continue;
252 : }
253 6 : Keywords keys;
254 6 : plumed_assert( actionRegister().check( interpreted[0] ) );
255 6 : actionRegister().getKeywords( interpreted[0], keys );
256 : std::string lab, myinputline;
257 12 : if( Tools::parse(interpreted, "LABEL", lab ) ) {
258 10 : myinputline = lab + ": ";
259 : }
260 6 : myinputline += interpreted[0] + " ";
261 : bool trailingcomment=false;
262 22 : for(unsigned i=1; i<interpreted.size(); ++i) {
263 16 : if( trailingcomment && interpreted[i]=="@newline") {
264 : trailingcomment=false;
265 0 : continue;
266 : }
267 16 : if( interpreted[i].find("#")!=std::string::npos ) {
268 : trailingcomment=true;
269 1 : continue;
270 : }
271 : // the words of a trailing comment are not part of the input
272 15 : if( trailingcomment ) {
273 2 : continue;
274 : }
275 13 : if( interpreted[i]=="@newline" || interpreted[i]=="..." ) {
276 3 : continue;
277 : }
278 : std::size_t pos = 0;
279 10 : while ((pos = interpreted[i].find("@newline",pos)) != std::string::npos) {
280 0 : interpreted[i].replace(pos, 8, "\n");
281 0 : pos++;
282 : }
283 20 : myinputline += interpreted[i] + " ";
284 : }
285 6 : if( status=="working" && keys.exists("IS_SHORTCUT") ) {
286 2 : myplumed.readInputLine( myinputline );
287 2 : ActionShortcut* as=dynamic_cast<ActionShortcut*>( myplumed.getActionSet()[myplumed.getActionSet().size()-1].get() );
288 2 : plumed_assert( as );
289 2 : const auto & shortcut_commands = as->getSavedInputLines();
290 : // some shortcuts do not save their expansion: keep the original line
291 2 : if( shortcut_commands.size()==0 ) {
292 2 : long_input.push_back( input[ll] );
293 : }
294 2 : for(unsigned i=0; i<shortcut_commands.size(); ++i) {
295 0 : std::vector<std::string> words = Tools::getWords( shortcut_commands[i] );
296 0 : long_input.push_back( words );
297 0 : }
298 : } else {
299 4 : long_input.push_back( input[ll] );
300 4 : myplumed.readInputLine( myinputline );
301 : }
302 7 : }
303 1 : return long_input;
304 6 : }
305 :
306 3 : void GenExample::printExampleInput( const std::vector<std::vector<std::string> >& input, const std::string& egname, const std::string& divname, std::ofstream& ofile ) {
307 3 : PlumedMain myplumed;
308 3 : int rr=sizeof(double), natoms=10000000;
309 3 : double kt=2.49;
310 3 : myplumed.cmd("setRealPrecision",&rr);
311 3 : if(Communicator::initialized()) {
312 0 : if(multi) {
313 0 : if(intracomm.Get_rank()==0) {
314 0 : myplumed.cmd("GREX setMPIIntercomm",&intercomm.Get_comm());
315 : }
316 0 : myplumed.cmd("GREX setMPIIntracomm",&intracomm.Get_comm());
317 0 : myplumed.cmd("GREX init");
318 : }
319 0 : myplumed.cmd("setMPIComm",&intracomm.Get_comm());
320 : }
321 3 : myplumed.cmd("setNatoms",&natoms);
322 3 : myplumed.cmd("setKbT",&kt);
323 3 : myplumed.cmd("init");
324 3 : std::vector<std::string> labellist;
325 : bool endplumed=false;
326 3 : ofile<<"<pre style=\"width: 97%;\" class=\"fragment\">"<<std::endl;
327 30 : for(unsigned ll=0; ll<input.size(); ++ll) {
328 27 : if( input[ll].empty() ) {
329 : ofile<<std::endl;
330 0 : continue;
331 : }
332 27 : if( input[ll][0].find("#")!=std::string::npos || endplumed ) {
333 6 : ofile<<"<span style=\"color:blue\">"<<input[ll][0];
334 18 : for(unsigned i=1; i<input[ll].size(); ++i) {
335 12 : ofile<<" "<<input[ll][i];
336 : }
337 6 : ofile<<"</span>"<<std::endl;;
338 : } else {
339 : // Interpret the label if this needs to be done
340 21 : std::vector<std::string> interpreted( input[ll] );
341 21 : Tools::interpretLabel(interpreted);
342 : std::string lab, myinputline;
343 : // Now read in the label
344 42 : if( Tools::parse(interpreted,"LABEL",lab) ) {
345 75 : ofile<<"<b name=\""<<egname<<lab<<"\" onclick=\'showPath(\""<<divname<<"\",\""<<egname<<lab<<"\")\'>"<<lab<<": </b>";
346 15 : labellist.push_back(lab);
347 30 : myinputline = lab + ": ";
348 : }
349 : // Print the keyword in use in the action
350 21 : std::string action = interpreted[0];
351 21 : myinputline += interpreted[0] + " ";
352 21 : if( action=="ENDPLUMED" ) {
353 : endplumed=true;
354 : }
355 21 : Keywords keys;
356 21 : actionRegister().getKeywords( interpreted[0], keys );
357 : // Handle conversion of action names to links
358 21 : std::transform(action.begin(), action.end(), action.begin(), [](unsigned char c) {
359 150 : return std::tolower(c);
360 : });
361 42 : ofile<<"<a href=\"https://www.plumed.org/doc-"<<version<<"/user-doc/html/";
362 : while(true) {
363 21 : std::size_t und=action.find_first_of("_");
364 21 : if( und==std::string::npos ) {
365 : break;
366 : }
367 0 : std::string first=action.substr(0,und);
368 0 : for(auto c : first ) {
369 0 : if( isdigit(c) ) {
370 0 : ofile<<c;
371 : } else {
372 0 : ofile<<"_"<<c;
373 : }
374 : }
375 0 : ofile<<"_";
376 0 : action=action.substr(und+1);
377 0 : }
378 171 : for(auto c : action ) {
379 150 : if( isdigit(c) ) {
380 0 : ofile<<c;
381 : } else {
382 150 : ofile<<"_"<<c;
383 : }
384 : }
385 42 : ofile<<".html\" style=\"color:green\">"<<interpreted[0]<<"</a> ";
386 : // And write out everything else in the input line
387 : bool trailingcomment=false;
388 69 : for(unsigned i=1; i<interpreted.size(); ++i) {
389 48 : if( interpreted[i]=="@newline" && i==1 ) {
390 6 : ofile<<"..."<<std::endl<<" ";
391 3 : continue;
392 45 : } else if( interpreted[i]=="@newline" ) {
393 3 : if( trailingcomment ) {
394 0 : ofile<<"</span>";
395 : trailingcomment=false;
396 : }
397 3 : if( i+1<interpreted.size() && interpreted[i+1]=="..." ) {
398 : ofile<<std::endl;
399 : } else {
400 0 : ofile<<std::endl<<" ";
401 : }
402 3 : continue;
403 42 : } else if( interpreted[i]=="__FILL__" ) {
404 0 : if( status!="incomplete" ) {
405 0 : error("found __FILL__ statement but status is " + status);
406 : }
407 0 : ofile<<"<span style=\"background-color:yellow\">__FILL__</span>";
408 0 : continue;
409 42 : } else if( interpreted[i]==action ) {
410 0 : continue;
411 : }
412 42 : if( interpreted[i].find("#")!=std::string::npos ) {
413 : trailingcomment=true;
414 3 : ofile<<"<span style=\"color:blue\">";
415 : }
416 :
417 39 : if( !trailingcomment ) {
418 33 : std::size_t eq=interpreted[i].find_first_of("=");
419 33 : if( eq!=std::string::npos ) {
420 27 : std::string keyword=interpreted[i].substr(0,eq), rest=interpreted[i].substr(eq+1);
421 81 : ofile<<"<span class=\"tooltip\">"<<keyword<<"<span class=\"right\">"<<keys.getTooltip(keyword)<<"<i></i></span></span>";
422 27 : if( rest=="__FILL__" ) {
423 0 : if( status!="incomplete" ) {
424 0 : error("found __FILL__ statement but status is " + status);
425 : }
426 0 : ofile<<"=<span style=\"background-color:yellow\">__FILL__</span>";
427 27 : } else if( rest.find_first_of("{")!=std::string::npos ) {
428 : std::size_t pos = 0;
429 0 : while ((pos = rest.find("@newline",pos)) != std::string::npos) {
430 0 : rest.replace(pos, 8, "\n");
431 0 : pos++;
432 : }
433 0 : ofile<<"="<<rest<<" ";
434 0 : myinputline += keyword + "=" + rest + " ";
435 : } else {
436 27 : std::vector<std::string> args=Tools::getWords(rest,"\t\n ,");
437 27 : ofile<<"=";
438 66 : for(unsigned ii=0; ii<args.size(); ++ii) {
439 : bool islabel=false;
440 : std::string thislab;
441 126 : for(unsigned j=0; j<labellist.size(); ++j) {
442 99 : std::size_t dot=args[ii].find_first_of(".");
443 99 : std::string lll=args[ii].substr(0,dot);
444 99 : if( lll==labellist[j] ) {
445 : islabel=true;
446 : thislab=labellist[j];
447 : break;
448 : }
449 : }
450 : if( islabel ) {
451 36 : ofile<<"<b name=\""<<egname<<thislab<<"\">"<<args[ii]<<"</b>";
452 : } else {
453 : ofile<<args[ii];
454 : }
455 39 : if( ii!=args.size()-1 ) {
456 12 : ofile<<",";
457 : }
458 : }
459 27 : myinputline += interpreted[i] + " ";
460 27 : }
461 27 : ofile<<" ";
462 6 : } else if( interpreted[i]!="@newline" && interpreted[i]!="..." ) {
463 3 : myinputline += interpreted[i] + " ";
464 12 : ofile<<"<span class=\"tooltip\">"<<interpreted[i]<<"<span class=\"right\">"<<keys.getTooltip(interpreted[i])<<"<i></i></span></span> ";
465 3 : } else if( interpreted[i]=="..." ) {
466 3 : ofile<<"...";
467 : }
468 : } else {
469 9 : ofile<<interpreted[i]<<" ";
470 : }
471 : }
472 21 : if( trailingcomment ) {
473 3 : ofile<<"</span>";
474 : }
475 : // This builds the hidden content that tells the user about what is calculated
476 21 : if( status=="working" ) {
477 28 : ofile<<"<span style=\"display:none;\" id=\""<<egname<<lab<<"\">";
478 42 : ofile<<"The "<<interpreted[0]<<" action with label <b>"<<lab<<"</b>";
479 14 : myplumed.readInputLine( myinputline );
480 14 : ActionWithValue* av=myplumed.getActionSet().selectWithLabel<ActionWithValue*>(lab);
481 : // virtual atoms are also ActionWithValue, but their components are not documented
482 14 : ActionWithVirtualAtom* avv=myplumed.getActionSet().selectWithLabel<ActionWithVirtualAtom*>(lab);
483 14 : if( avv ) {
484 2 : ofile<<" calculates the position of a virtual atom";
485 12 : } else if( av ) {
486 6 : if( av->getNumberOfComponents()==1 ) {
487 2 : ofile<<" calculates a single scalar value";
488 4 : } else if( av->getNumberOfComponents()>0 ) {
489 4 : ofile<<" calculates the following quantities:"<<std::endl;
490 4 : ofile<<"<table align=\"center\" frame=\"void\" width=\"95%\" cellpadding=\"5%\">"<<std::endl;
491 4 : ofile<<"<tr><td width=\"5%\"><b> Quantity </b> </td><td><b> Description </b> </td></tr>"<<std::endl;
492 4 : unsigned ncomp = av->getNumberOfComponents();
493 14 : for(unsigned k=0; k<ncomp; ++k ) {
494 10 : std::string myname = av->copyOutput(k)->getName();
495 10 : std::size_t dot=myname.find_first_of(".");
496 10 : std::string tname=myname.substr(dot+1);
497 40 : ofile<<"<tr><td width=\"5%\">"<<myname<<"</td><td>"<<av->getOutputComponentDescription(tname,keys)<<"</td></tr>"<<std::endl;
498 : }
499 4 : ofile<<"</table>"<<std::endl;
500 : }
501 6 : } else if( interpreted[0]=="GROUP" ) {
502 2 : ofile<<" defines a group of atoms so that they can be referred to later in the input";
503 : }
504 14 : ofile<<"</span>"<<std::endl;
505 : } else {
506 14 : ofile<<"<span style=\"display:none;\" id=\""<<egname<<lab<<"\"> You cannot view the components that are calculated by each action for this input file. Sorry </span>"<<std::endl;
507 : }
508 42 : }
509 27 : ofile.flush();
510 : }
511 3 : ofile<<"</pre>"<<std::endl;
512 3 : }
513 :
514 : } // End of namespace
515 : }
|