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