1 /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2    Copyright (c) 2019-2021 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 "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/IFile.h"
33 #include <cstdio>
34 #include <string>
35 #include <vector>
36 #include <iostream>
37 #include <fstream>
38 
39 using namespace std;
40 
41 namespace PLMD {
42 namespace cltools {
43 
44 //+PLUMEDOC TOOLS gen_example
45 /*
46 gen_example is a tool that you can use to construct an example for the manual that users can interact with to understand
47 
48 The example constructed by this action is in html. In all probability you will never need to use this
49 tool. However, it is used within the scripts that generate the html manual for PLUMED.  If you need to use this
50 tool outside those scripts the input is specified using the following command line arguments.
51 
52 \par Examples
53 
54 The following generates an example based on the contents of the plumed file plumed.dat
55 \verbatim
56 plumed gen_example --plumed plumed.dat --status working
57 \endverbatim
58 
59 
60 */
61 //+ENDPLUMEDOC
62 
63 class GenExample:
64   public CLTool
65 {
66 private:
67   int multi;
68   std::string status, version;
69   Communicator intracomm;
70   Communicator intercomm;
71 public:
72   static void registerKeywords( Keywords& keys );
73   explicit GenExample(const CLToolOptions& co );
74   int main(FILE* in, FILE*out,Communicator& pc) override;
description() const75   string description()const override {
76     return "construct an example for the manual that users can interact with";
77   }
78   void printExampleInput( const std::vector<std::vector<std::string> >& input, const std::string& egname, const std::string& divname, std::ofstream& ofile );
79   std::vector<std::vector<std::string> > createLongInput( const std::vector<std::vector<std::string> >& input );
80 };
81 
82 PLUMED_REGISTER_CLTOOL(GenExample,"gen_example")
83 
registerKeywords(Keywords & keys)84 void GenExample::registerKeywords( Keywords& keys ) {
85   CLTool::registerKeywords( keys );
86   keys.add("compulsory","--plumed","plumed.dat","convert the input in this file to the html manual");
87   keys.add("compulsory","--out","example.html","the file on which to output the example in html");
88   keys.add("compulsory","--name","ppp","the name to use for this particular input");
89   keys.add("compulsory","--status","nobadge","whether or not the input file works");
90   keys.add("compulsory","--multi","0","set number of replicas for multi environment (needs MPI)");
91 }
92 
GenExample(const CLToolOptions & co)93 GenExample::GenExample(const CLToolOptions& co ):
94   CLTool(co),
95   multi(0),
96   status("nobadge"),
97   version("master")
98 {
99   inputdata=commandline;
100 }
101 
main(FILE * in,FILE * out,Communicator & pc)102 int GenExample::main(FILE* in, FILE*out,Communicator& pc) {
103 
104 // set up for multi replica driver:
105   parse("--multi",multi);
106   if(multi) {
107     int ntot=pc.Get_size(); int nintra=ntot/multi;
108     if(multi*nintra!=ntot) error("invalid number of processes for multi environment");
109     pc.Split(pc.Get_rank()/nintra,pc.Get_rank(),intracomm);
110     pc.Split(pc.Get_rank()%nintra,pc.Get_rank(),intercomm);
111   } else {
112     intracomm.Set_comm(pc.Get_comm());
113   }
114 
115   if( config::getVersionLong().find("dev")==std::string::npos ) version="v"+config::getVersion();
116   std::string fname, egname, outfile; parse("--plumed",fname);
117   parse("--name",egname); parse("--out",outfile); parse("--status",status);
118 
119   int r=0;
120   if(intracomm.Get_rank()==0) r=intercomm.Get_rank();
121   intracomm.Bcast(r,0);
122   if(r>0) outfile="/dev/null";
123 
124   IFile ifile; ifile.open(fname); ifile.allowNoEOL(); std::ofstream ofile; ofile.open(outfile); std::vector<bool> shortcuts;
125   bool hasshortcuts=false, endplumed=false; std::vector<std::vector<std::string> > input; std::vector<std::string> words;
126   while( Tools::getParsedLine(ifile, words, false) ) {
127     input.push_back( words ); shortcuts.push_back( false );
128     if( words.empty() || words[0].find("#")!=std::string::npos || endplumed ) continue;
129     std::vector<std::string> interpreted( words ); Tools::interpretLabel(interpreted);
130     if( interpreted[0]=="ENDPLUMED" ) { endplumed=true; continue; }
131     Keywords keys; actionRegister().getKeywords( interpreted[0], keys );
132     if( status=="working" && keys.exists("IS_SHORTCUT") ) hasshortcuts=shortcuts[shortcuts.size()-1]=true;
133   }
134   ifile.close();
135   if( hasshortcuts ) {
136     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";
137     ofile<<"<div style=\"width: 10%; float:left\"><button type=\"button\" id=\""<<egname<<"_button\" onclick=\'swapInput(\""<<egname<<"\")\'>contract shortcuts</button></div>";
138   } else {
139     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";
140   }
141   ofile<<"<div style=\"width: 10%; float:left\">";
142   ofile<<"<img src=\"https://img.shields.io/badge/";
143   if(status=="working") ofile<<version<<"-passing-green";
144   else if(status=="broken") ofile<<version<<"-failed-red";
145   else if(status=="loads") ofile<<"with-LOAD-yellow";
146   else if(status=="incomplete") ofile<<version<<"-incomplete-yellow";
147   else error("unknown status");
148   ofile<<".svg\" alt=\"tested on "<<version<<"\" /></div>";
149   ofile.flush();
150   if( hasshortcuts ) {
151     // Write out the short version of the input
152     ofile<<"<div style=\"width: 100%; float:left\" id=\"input_"<<egname<<"\"></div>"<<std::endl;
153     // Write an extra pre to make sure the html after the example is put in the right place on the page
154     ofile<<"<pre style=\"width: 97%;\" class=\"fragment\"></pre>"<<std::endl;
155     ofile<<"<script type=\"text/javascript\">"<<std::endl;
156     ofile<<"if (window.addEventListener) { // Mozilla, Netscape, Firefox"<<std::endl;
157     ofile<<"    window.addEventListener('load', "<<egname<<"Load, false);"<<std::endl;
158     ofile<<"} else if (window.attachEvent) { // IE"<<std::endl;
159     ofile<<"    window.attachEvent('onload', "<<egname<<"Load);"<<std::endl;
160     ofile<<"}"<<std::endl;
161     ofile<<"function "<<egname<<"Load(event) {"<<std::endl;
162     ofile<<"       swapInput(\""<<egname<<"\");"<<std::endl;
163     ofile<<"}"<<std::endl;
164     ofile<<"</script>"<<std::endl;
165     ofile<<"<div style=\"display:none;\" id=\""<<egname<<"short\">"<<std::endl;
166     printExampleInput( input, egname + "short", egname, ofile );
167     ofile<<"</div>"<<std::endl;
168     // Write out long version of the input
169     ofile<<"<div style=\"display:none;\" id=\""<<egname<<"long\">";
170     std::vector<std::vector<std::string> > long_input = createLongInput( input );
171     printExampleInput( long_input, egname + "long", egname, ofile );
172     ofile<<"</div>"<<std::endl;
173   } else printExampleInput( input, egname, egname, ofile );
174   ofile.close(); return 0;
175 }
176 
createLongInput(const std::vector<std::vector<std::string>> & input)177 std::vector<std::vector<std::string> > GenExample::createLongInput( const std::vector<std::vector<std::string> >& input ) {
178   std::vector<std::vector<std::string> > long_input; PlumedMain myplumed; int rr=sizeof(double), natoms=10000000; double kt=2.49;
179   myplumed.cmd("setRealPrecision",&rr);
180   if(Communicator::initialized()) {
181     if(multi) {
182       if(intracomm.Get_rank()==0) myplumed.cmd("GREX setMPIIntercomm",&intercomm.Get_comm());
183       myplumed.cmd("GREX setMPIIntracomm",&intracomm.Get_comm()); myplumed.cmd("GREX init");
184     }
185     myplumed.cmd("setMPIComm",&intracomm.Get_comm());
186   }
187   bool endplumed=false; myplumed.cmd("setNatoms",&natoms); myplumed.cmd("setKbT",&kt); myplumed.cmd("init");
188   for(unsigned ll=0; ll<input.size(); ++ll) {
189     if( input[ll].empty() || endplumed ) { long_input.push_back( input[ll] ); continue; }
190     if( input[ll][0].find("#")!=std::string::npos ) { long_input.push_back( input[ll] ); continue; }
191     std::vector<std::string> interpreted( input[ll] ); Tools::interpretLabel(interpreted);
192     if( interpreted[0]=="ENDPLUMED" ) { endplumed=true; long_input.push_back( input[ll] ); continue; }
193     Keywords keys; plumed_assert( actionRegister().check( interpreted[0] ) );
194     actionRegister().getKeywords( interpreted[0], keys ); std::string lab, myinputline;
195     if( Tools::parse(interpreted, "LABEL", lab ) ) myinputline = lab + ": ";
196     myinputline += interpreted[0] + " "; bool trailingcomment=false;
197     for(unsigned i=1; i<interpreted.size(); ++i) {
198       if( trailingcomment && interpreted[i]=="@newline") { trailingcomment=false; continue; }
199       if( interpreted[i].find("#")!=std::string::npos ) { trailingcomment=true; continue; }
200       if( interpreted[i]=="@newline" || interpreted[i]=="..." ) continue;
201       std::size_t pos = 0;  while ((pos = interpreted[i].find("@newline",pos)) != std::string::npos) { interpreted[i].replace(pos, 8, "\n"); pos++; }
202       myinputline += interpreted[i] + " ";
203     }
204     if( status=="working" && keys.exists("IS_SHORTCUT") ) {
205       myplumed.readInputLine( myinputline );
206       ActionShortcut* as=dynamic_cast<ActionShortcut*>( myplumed.getActionSet()[myplumed.getActionSet().size()-1].get() );
207       plumed_assert( as ); std::vector<std::string> shortcut_commands = as->getSavedInputLines();
208       for(unsigned i=0; i<shortcut_commands.size(); ++i) {
209         std::vector<std::string> words = Tools::getWords( shortcut_commands[i] ); long_input.push_back( words );
210       }
211     } else { long_input.push_back( input[ll] ); myplumed.readInputLine( myinputline ); }
212   }
213   return long_input;
214 }
215 
printExampleInput(const std::vector<std::vector<std::string>> & input,const std::string & egname,const std::string & divname,std::ofstream & ofile)216 void GenExample::printExampleInput( const std::vector<std::vector<std::string> >& input, const std::string& egname, const std::string& divname, std::ofstream& ofile ) {
217   PlumedMain myplumed; int rr=sizeof(double), natoms=10000000; double kt=2.49;
218   myplumed.cmd("setRealPrecision",&rr);
219   if(Communicator::initialized()) {
220     if(multi) {
221       if(intracomm.Get_rank()==0) myplumed.cmd("GREX setMPIIntercomm",&intercomm.Get_comm());
222       myplumed.cmd("GREX setMPIIntracomm",&intracomm.Get_comm()); myplumed.cmd("GREX init");
223     }
224     myplumed.cmd("setMPIComm",&intracomm.Get_comm());
225   }
226   myplumed.cmd("setNatoms",&natoms); myplumed.cmd("setKbT",&kt); myplumed.cmd("init");
227   std::vector<std::string> labellist; bool endplumed=false;
228   ofile<<"<pre style=\"width: 97%;\" class=\"fragment\">"<<std::endl;
229   for(unsigned ll=0; ll<input.size(); ++ll) {
230     if( input[ll].empty() ) { ofile<<std::endl; continue; }
231     if( input[ll][0].find("#")!=std::string::npos || endplumed ) {
232       ofile<<"<span style=\"color:blue\">"<<input[ll][0];
233       for(unsigned i=1; i<input[ll].size(); ++i) ofile<<" "<<input[ll][i];
234       ofile<<"</span>"<<std::endl;;
235     } else {
236       // Interpret the label if this needs to be done
237       std::vector<std::string> interpreted( input[ll] ); Tools::interpretLabel(interpreted); std::string lab, myinputline;
238       // Now read in the label
239       if( Tools::parse(interpreted,"LABEL",lab) ) {
240         ofile<<"<b name=\""<<egname<<lab<<"\" onclick=\'showPath(\""<<divname<<"\",\""<<egname<<lab<<"\")\'>"<<lab<<": </b>";
241         labellist.push_back(lab); myinputline = lab + ": ";
242       }
243       // Print the keyword in use in the action
244       std::string action = interpreted[0]; myinputline += interpreted[0] + " ";
245       if( action=="ENDPLUMED" ) endplumed=true;
246       Keywords keys; actionRegister().getKeywords( interpreted[0], keys );
247       // Handle conversion of action names to links
248       std::transform(action.begin(), action.end(), action.begin(), [](unsigned char c) { return std::tolower(c); });
249       ofile<<"<a href=\"https://www.plumed.org/doc-"<<version<<"/user-doc/html/";
250       for(unsigned n=0;; ++n) {
251         std::size_t und=action.find_first_of("_");
252         if( und==std::string::npos ) break;
253         std::string first=action.substr(0,und);
254         for(auto c : first ) { if( isdigit(c) ) ofile<<c; else ofile<<"_"<<c; }
255         ofile<<"_"; action=action.substr(und+1);
256       }
257       for(auto c : action ) { if( isdigit(c) ) ofile<<c; else ofile<<"_"<<c; }
258       ofile<<".html\" style=\"color:green\">"<<interpreted[0]<<"</a> ";
259       // And write out everything else in the input line
260       bool trailingcomment=false;
261       for(unsigned i=1; i<interpreted.size(); ++i) {
262         if( interpreted[i]=="@newline" && i==1 ) { ofile<<"..."<<std::endl<<"   "; continue; }
263         else if( interpreted[i]=="@newline" ) {
264           if( trailingcomment ) { ofile<<"</span>"; trailingcomment=false; }
265           if( interpreted[i+1]=="..." ) ofile<<std::endl;
266           else ofile<<std::endl<<"   ";
267           continue;
268         } else if( interpreted[i]=="__FILL__" ) {
269           if( status!="incomplete" ) error("found __FILL__ statement but status is " + status);
270           ofile<<"<span style=\"background-color:yellow\">__FILL__</span>";
271           continue;
272         } else if( interpreted[i]==action ) continue;
273         if( interpreted[i].find("#")!=std::string::npos ) { trailingcomment=true; ofile<<"<span style=\"color:blue\">"; }
274 
275         if( !trailingcomment ) {
276           std::size_t eq=interpreted[i].find_first_of("=");
277           if( eq!=std::string::npos ) {
278             std::string keyword=interpreted[i].substr(0,eq), rest=interpreted[i].substr(eq+1);
279             ofile<<"<div class=\"tooltip\">"<<keyword<<"<div class=\"right\">"<<keys.getTooltip(keyword)<<"<i></i></div></div>";
280             if( rest=="__FILL__" ) {
281               if( status!="incomplete" ) error("found __FILL__ statement but status is " + status);
282               ofile<<"=<span style=\"background-color:yellow\">__FILL__</span>";
283             } else if( rest.find_first_of("{")!=std::string::npos ) {
284               std::size_t pos = 0;  while ((pos = rest.find("@newline",pos)) != std::string::npos) { rest.replace(pos, 8, "\n"); pos++; }
285               ofile<<"="<<rest<<" "; myinputline += keyword + "=" + rest + " ";
286             } else {
287               std::vector<std::string> args=Tools::getWords(rest,"\t\n ,"); ofile<<"=";
288               for(unsigned i=0; i<args.size(); ++i) {
289                 bool islabel=false; std::string thislab;
290                 for(unsigned j=0; j<labellist.size(); ++j) {
291                   std::size_t dot=args[i].find_first_of("."); std::string lll=args[i].substr(0,dot);
292                   if( lll==labellist[j] ) { islabel=true; thislab=labellist[j]; break; }
293                 }
294                 if( islabel ) ofile<<"<b name=\""<<egname<<thislab<<"\">"<<args[i]<<"</b>";
295                 else ofile<<args[i];
296                 if( i!=args.size()-1 ) ofile<<",";
297               }
298               myinputline += interpreted[i] + " ";
299             }
300             ofile<<" ";
301           } else if( interpreted[i]!="@newline" && interpreted[i]!="..." ) {
302             myinputline += interpreted[i] + " ";
303             ofile<<"<div class=\"tooltip\">"<<interpreted[i]<<"<div class=\"right\">"<<keys.getTooltip(interpreted[i])<<"<i></i></div></div> ";
304           } else if( interpreted[i]=="..." ) ofile<<"...";
305         } else ofile<<interpreted[i]<<" ";
306       }
307       if( trailingcomment ) ofile<<"</span>";
308       // This builds the hidden content that tells the user about what is calculated
309       if( status=="working" ) {
310         ofile<<"<span style=\"display:none;\" id=\""<<egname<<lab<<"\">";
311         ofile<<"The "<<interpreted[0]<<" action with label <b>"<<lab<<"</b>";
312         myplumed.readInputLine( myinputline );
313         ActionWithValue* av=dynamic_cast<ActionWithValue*>( myplumed.getActionSet().selectWithLabel<Action*>(lab) );
314         if( av ) {
315           if( av->getNumberOfComponents()==1 ) { ofile<<" calculates a single scalar value"; }
316           else if( av->getNumberOfComponents()>0 ) {
317             ofile<<" calculates the following quantities:"<<std::endl;
318             ofile<<"<table  align=\"center\" frame=\"void\" width=\"95%%\" cellpadding=\"5%%\">"<<std::endl;
319             ofile<<"<tr><td width=\"5%%\"><b> Quantity </b>  </td><td><b> Description </b> </td></tr>"<<std::endl;
320             unsigned ncomp = av->getNumberOfComponents();
321             for(unsigned k=0; k<ncomp; ++k ) {
322               std::string myname = av->copyOutput(k)->getName(); std::size_t dot=myname.find_first_of(".");
323               std::string tname=myname.substr(dot+1); std::size_t und=tname.find_first_of("_"); std::size_t hyph=tname.find_first_of("-");
324               if( und!=std::string::npos && hyph!=std::string::npos ) plumed_merror("cannot use underscore and hyphen in name");
325               ofile<<"<tr><td width=\"5%%\">"<<myname<<"</td><td>";
326               if( und!=std::string::npos ) {
327                 ofile<<keys.getOutputComponentDescription(tname.substr(und))<<" This particular component measures this quantity for the input CV named ";
328                 ofile<<tname.substr(0,und);
329               } else if( hyph!=std::string::npos ) {
330                 ofile<<keys.getOutputComponentDescription(tname.substr(0,hyph))<<"  This is the "<<tname.substr(hyph+1)<<"th of these quantities";
331               } else ofile<<keys.getOutputComponentDescription(tname);
332               ofile<<"</td></tr>";
333             }
334             ofile<<"</table>"<<std::endl;
335           }
336         } else {
337           ActionWithVirtualAtom* avv=dynamic_cast<ActionWithVirtualAtom*>( myplumed.getActionSet().selectWithLabel<Action*>(lab) );
338           if( avv ) ofile<<" calculates the position of a virtual atom";
339           else if( interpreted[0]=="GROUP" ) ofile<<" defines a group of atoms so that they can be referred to later in the input";
340         }
341         ofile<<"</span>"<<std::endl;
342       } else if( status!="working" ) {
343         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;
344       } else ofile<<std::endl;
345     }
346     ofile.flush();
347   }
348   ofile<<"</pre>"<<std::endl;
349 }
350 
351 } // End of namespace
352 }
353