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