1 /*
2  * Stellarium
3  * Copyright (C) 2007 Fabien Chereau, 2009 Matthew Gates
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335, USA.
18  */
19 
20 #ifndef STELSCRIPTMGR_HPP
21 #define STELSCRIPTMGR_HPP
22 
23 #include <QObject>
24 #include <QStringList>
25 #include <QFile>
26 #include <QTimer>
27 #include <QEventLoop>
28 #include <QMap>
29 #include <QPair>
30 #include <QSet>
31 
32 // class StelMainScriptAPI;
33 #include "StelMainScriptAPI.hpp"
34 
35 class StelScriptEngineAgent;
36 class QScriptEngine;
37 
38 #ifdef ENABLE_SCRIPT_CONSOLE
39 class ScriptConsole;
40 #endif
41 
42 //! Manage scripting in Stellarium
43 class StelScriptMgr : public QObject
44 {
45 	Q_OBJECT
46 
47 	Q_PROPERTY(QString runningScriptId READ runningScriptId NOTIFY runningScriptIdChanged)
48 
49 #ifdef ENABLE_SCRIPT_CONSOLE
50 friend class ScriptConsole;
51 #endif
52 
53 public:
54 	StelScriptMgr(QObject *parent=Q_NULLPTR);
55 	~StelScriptMgr();
56 
57 	QStringList getScriptList() const;
58 
59 	//! Find out if a script is running
60 	//! @return true if a script is running, else false
61 	bool scriptIsRunning() const;
62 	//! Get the ID (usually filename) of the currently running script
63 	//! @return Empty string if no script is running, else the
64 	//! ID of the script which is running.
65 	QString runningScriptId() const;
66 
67 	// Pre-processor functions
68 	//! Preprocess script, esp. process include instructions.
69 	//! if the command line option --verbose has been given,
70 	//! this dumps the preprocessed script with line numbers attached to log.
71 	//! This helps to understand the line number given by the usual error message.
72 	bool preprocessScript(const QString fileName, const QString& input, QString& output, const QString& scriptDir, int &errLoc);
73 	bool preprocessFile(const QString fileName, QFile &input, QString& output, const QString& scriptDir);
74 
75 	//! Add all the StelModules into the script engine
76 	void addModules();
77 
78     //! Define JS classes Vec3f, Vec3d
79 	static void defVecClasses(QScriptEngine *engine);
80 
81     //! Permit access to StelScriptMainAPI's methods
getMetaOfStelMainScriptAPI()82 	const QMetaObject * getMetaOfStelMainScriptAPI(){ return mainAPI->metaObject(); }
83 
84     //! Accessor to QEventLoop
getWaitEventLoop()85     QEventLoop* getWaitEventLoop(){ return waitEventLoop; }
86 
87 public slots:
88 	//! Returns a HTML description of the specified script.
89 	//! Includes name, author, description...
90 	//! @param s the file name of the script whose HTML description is to be returned.
91 	//! @param generateDocumentTags if true, the main wrapping document tags (\<html\>\<body\>...\</body\>\</html\>) are also generated
92 	QString getHtmlDescription(const QString& s, bool generateDocumentTags=true) const;
93 
94 	//! Gets a single line name of the script.
95 	//! @param s the file name of the script whose name is to be returned.
96 	//! @return text following a comment with Name: at the start.  If no
97 	//! such comment is found, the file name will be returned.  If the file
98 	//! is not found or cannot be opened for some reason, an Empty string
99 	//! will be returned.
100 	QString getName(const QString& s) const;
101 
102 	//! Gets the name of the script Author
103 	//! @param s the file name of the script whose name is to be returned.
104 	//! @return text following a comment with Author: at the start.  If no
105 	//! such comment is found, "" is returned.  If the file
106 	//! is not found or cannot be opened for some reason, an Empty string
107 	//! will be returned.
108 	QString getAuthor(const QString& s) const;
109 
110 	//! Gets the licensing terms for the script
111 	//! @param s the file name of the script whose name is to be returned.
112 	//! @return text following a comment with License: at the start.  If no
113 	//! such comment is found, "" is returned.  If the file
114 	//! is not found or cannot be opened for some reason, an Empty string
115 	//! will be returned.
116 	QString getLicense(const QString& s) const;
117 
118 	//! Gets the version of the script
119 	//! @param s the file name of the script whose name is to be returned.
120 	//! @return text following a comment with Version: at the start.  If no
121 	//! such comment is found, "" is returned.  If the file
122 	//! is not found or cannot be opened for some reason, an Empty string
123 	//! will be returned.
124 	QString getVersion(const QString& s) const;
125 
126 	//! Gets a description of the script.
127 	//! @param s the file name of the script whose name is to be returned.
128 	//! @return text following a comment with Description: at the start.
129 	//! The description is considered to be over when a line with no comment
130 	//! is found.  If no such comment is found, QString("") is returned.
131 	//! If the file is not found or cannot be opened for some reason, an
132 	//! Empty string will be returned.
133 	QString getDescription(const QString& s) const;
134 
135 	//! Gets the default shortcut of the script.
136 	//! @param s the file name of the script whose name is to be returned.
137 	//! @return text following a comment with Shortcut: at the start.
138 	//! If no such comment is found, QString("") is returned.
139 	//! If the file is not found or cannot be opened for some reason, an
140 	//! Empty string will be returned.
141 	QString getShortcut(const QString& s) const;
142 
143 	//! Run the script located in the given file. In essence, this calls prepareScript and runPreprocessedScript.
144 	//! @note This is a blocking call! The event queue is held up by calls of QCoreApplication::processEvents().
145 	//! @param fileName the location of the file containing the script.
146 	//! @param includePath the directory to use when searching for include files
147 	//! in the SSC preprocessor. If empty, this will be the same as the
148 	//! directory where the script file resides. If you're running a generated script from
149 	//! a temp directory, but want to include a file from elsewhere, it
150 	//! can be usetul to set it to something else (e.g. in ScriptConsole).
151 	//! @return false if the named script could not be prepared or run, true otherwise
152 	bool runScript(const QString& fileName, const QString& includePath="");
153 
154 	//! Runs the script code given. This can be used for quick script executions, without having to create a
155 	//! temporary file first.
156 	//! @note This is a blocking call! The event queue is held up by calls of QCoreApplication::processEvents().
157 	//! @param scriptId path name, if available, or something helpful
158 	//! @param scriptCode The script to execute
159 	//! @param errLoc offset of erroneous include line, or -1
160 	//! @param includePath If a null string (the default), no pre-processing is done. If an empty string, the default
161 	//! script directories are used (script/ in both user and install directory). Otherwise, the given directory is used.
162 	//! @return false if the named script code could not be prepared or run, true otherwise
163 	bool runScriptDirect(const QString scriptId, const QString& scriptCode, int &errLoc, const QString &includePath = QString());
164 
165 	//! Convenience method similar to runScriptDirect(const QString scriptId, const QString& scriptCode, int &errLoc, const QString &includePath = QString());
166 	//! when scriptId and errLoc are not relevant. (Required e.g. in RemoteControl)
167 	bool runScriptDirect(const QString& scriptCode, const QString &includePath = QString());
168 
169 	//! Runs preprocessed script code which has been generated using runPreprocessedScript().
170 	//! In general, you do not want to use this method, use runScript() or runScriptDirect() instead.
171 	//! @note This is a blocking call! The event queue is held up by calls of QCoreApplication::processEvents().
172 	//! @param preprocessedScript the string containing the preprocessed script.
173 	//! @param scriptId The name of the script. Usually should correspond to the file name.
174 	//! @return false if the given script code could not be run, true otherwise
175 	bool runPreprocessedScript(const QString& preprocessedScript, const QString &scriptId);
176 
177 	//! Loads a script file and does all preparatory steps except for actually executing the script in the engine.
178 	//! Use runPreprocessedScript to execute the script.
179 	//! It should be safe to call this method from another thread.
180 	//! @param script returns the preprocessed script text
181 	//! @param fileName the location of the file containing the script.
182 	//! @param includePath the directory to use when searching for include files
183 	//! in the SSC preprocessor.  Usually this will be the same as the
184 	//! script file itself, but if you're running a generated script from
185 	//! a temp directory, but want to include a file from elsewhere, it
186 	//! can be usetul to set it to something else (e.g. in ScriptConsole).
187 	//! @return false if the named script could not be prepared, true otherwise
188 	bool prepareScript(QString& script, const QString& fileName, const QString& includePath="");
189 
190 	//! Stops any running script.
191 	//! @return false if no script was running, true otherwise.
192 	void stopScript();
193 
194 	//! Changes the rate at which the script executes as a multiple of real time.
195 	//! Note that this is not the same as the rate at which simulation time passes
196 	//! because the script running at normal rate might set the simulation time rate
197 	//! to be non-real time.
198 	//! @param r rate, e.g. 2.0 = script runs at twice the normal rate
199 	void setScriptRate(double r);
200 
201 	//! Get the rate at which the script is running as a multiple of the normal
202 	//! execution rate.
203 	double getScriptRate() const;
204 
205 	//! cause the emission of the scriptDebug signal. This is so that functions in
206 	//! StelMainScriptAPI can explicitly send information to the ScriptConsole
207 	void debug(const QString& msg);
208 
209 	//! cause the emission of the scriptOutput signal. This is so that functions in
210 	//! StelMainScriptAPI can explicitly send information to the ScriptConsole
211 	void output(const QString& msg);
212 
213 	//! Reset output file and cause the emission of an (empty) scriptOutput signal.
214 	void resetOutput(void);
215 
216 	//! Save output file to new file.
217 	//! This is required to allow reading with other program on Windows while output.txt is still open.
218 	//! @param filename new filename. If this is not an absolute path, it will be created in the same directory as output.txt
219 	//! @note For storing to absolute path names, set [scripts]/flag_script_allow_write_absolute_path=true.
220 	void saveOutputAs(const QString &filename);
221 
222 	//! Pause a running script.
223 	void pauseScript();
224 
225 	//! Resume a paused script.
226 	void resumeScript();
227 
228 private slots:
229 	//! Called at the end of the running threa
230 	void scriptEnded();
231 signals:
232 	//! Emitted when the running script id changes (also on start/stop)
233 	void runningScriptIdChanged(const QString& id);
234 	//! Notification when a script starts running
235 	void scriptRunning();
236 	//! Notification when a script has paused running
237 	void scriptPaused();
238 	//! Notification when a script has stopped running
239 	void scriptStopped();
240 	//! Notification of a script event - warnings, current execution line etc.
241 	void scriptDebug(const QString&) const;
242 	//! Notification of a script event - output line.
243 	void scriptOutput(const QString&) const;
244 
245 private:
246 	// Utility functions for preprocessor
247 	QMap<QString, QString> mappify(const QStringList& args, bool lowerKey=false);
248 	bool strToBool(const QString& str);
249 	// The recursive preprocessing workhorse.
250 	void expand(const QString fileName, const QString &input, QString &output, const QString &scriptDir, int &errLoc);
251 
252 	//! Generate one StelAction per script.
253 	//! The name of the action is of the form: "actionScript/<script-path>"
254 	void initActions();
255 
256 	//! This function is for use with getName, getAuthor and getLicense.
257 	//! @param s the script id
258 	//! @param id the command line id, e.g. "Name"
259 	//! @param notFoundText the text to be returned if the key is not found
260 	//! @return the text following the id and : on a comment line near the top of
261 	//! the script file (i.e. before there is a non-comment line).
262 	QString getHeaderSingleLineCommentText(const QString& s, const QString& id, const QString& notFoundText="") const;
263 	QScriptEngine* engine;
264 
265 	//! The thread in which scripts are run
266 	StelMainScriptAPI *mainAPI;
267 
268 	//! The QEventLoop for wait and waitFor
269     QEventLoop* waitEventLoop;
270 
271 	QString scriptFileName;
272 
273 	//Script engine agent
274 	StelScriptEngineAgent *agent;
275 
276 	// Map line numbers of output to <path>:<line>
277 	int outline;
278 	QMap<int,QPair<QString,int>> num2loc;
279 	QString lookup( int outline );
280 
281 	// Registry for include files
282 	QSet<QString> includeSet;
283 };
284 
285 #endif // STELSCRIPTMGR_HPP
286 
287