Revision 844 org.gvsig.scripting/trunk/org.gvsig.scripting/org.gvsig.scripting.app/org.gvsig.scripting.app.mainplugin/src/main/java/org/gvsig/scripting/app/extension/ScriptingExtension.java
ScriptingExtension.java | ||
---|---|---|
41 | 41 |
import java.util.List; |
42 | 42 |
|
43 | 43 |
import javax.swing.JOptionPane; |
44 |
import javax.swing.SwingUtilities; |
|
44 | 45 |
import org.apache.commons.io.FileUtils; |
45 | 46 |
import org.apache.commons.io.IOUtils; |
46 | 47 |
import org.apache.commons.lang3.StringUtils; |
... | ... | |
56 | 57 |
import org.gvsig.scripting.ScriptingFolder; |
57 | 58 |
import org.gvsig.scripting.ScriptingLocator; |
58 | 59 |
import org.gvsig.scripting.ScriptingManager; |
60 |
import org.gvsig.scripting.ScriptingScript; |
|
61 |
import org.gvsig.scripting.ScriptingUnit; |
|
59 | 62 |
import org.gvsig.scripting.swing.api.JScriptingComposer; |
60 | 63 |
import org.gvsig.scripting.swing.api.ScriptingSwingLocator; |
61 | 64 |
import org.gvsig.scripting.swing.api.ScriptingUIManager; |
65 |
import static org.gvsig.scripting.swing.api.ScriptingUIManager.SCRIPT_COMPOSER_AUTORUN; |
|
62 | 66 |
import org.gvsig.tools.dynobject.DynObject; |
63 | 67 |
import org.gvsig.tools.swing.api.ToolsSwingLocator; |
64 | 68 |
import org.gvsig.tools.swing.api.windowmanager.WindowManager; |
... | ... | |
66 | 70 |
import org.slf4j.Logger; |
67 | 71 |
import org.slf4j.LoggerFactory; |
68 | 72 |
import org.gvsig.tools.ToolsLocator; |
73 |
import org.gvsig.tools.exception.BaseException; |
|
69 | 74 |
import org.gvsig.tools.i18n.I18nManager; |
70 | 75 |
import org.gvsig.tools.script.Script; |
76 |
import org.gvsig.tools.task.SimpleTaskStatus; |
|
71 | 77 |
import org.gvsig.tools.util.Invocable; |
78 |
import org.gvsig.tools.visitor.VisitCanceledException; |
|
79 |
import org.gvsig.tools.visitor.Visitor; |
|
72 | 80 |
|
73 | 81 |
public class ScriptingExtension extends Extension { |
74 | 82 |
|
75 | 83 |
private static final Logger logger = LoggerFactory.getLogger(ScriptingExtension.class); |
76 | 84 |
|
77 | 85 |
private PluginServices plugin = null; |
78 |
|
|
86 |
|
|
79 | 87 |
/* |
80 | 88 |
* la funcion log y las constantes estan pensadas para usarlas desde los scripts. |
81 |
*/ |
|
89 |
*/
|
|
82 | 90 |
public static final int INFO = 0; |
83 | 91 |
public static final int TRACE = 1; |
84 | 92 |
public static final int WARN = 2; |
85 | 93 |
public static final int ERROR = 3; |
86 |
|
|
94 |
private static boolean composer_initialized = false; |
|
95 |
|
|
87 | 96 |
public static void log(String message) { |
88 |
log(INFO,message,null);
|
|
97 |
log(INFO, message, null);
|
|
89 | 98 |
} |
90 |
|
|
99 |
|
|
91 | 100 |
public static void log(int level, String message) { |
92 |
log(level,message,null);
|
|
101 |
log(level, message, null);
|
|
93 | 102 |
} |
94 |
|
|
103 |
|
|
95 | 104 |
public static void log(int level, String message, Throwable th) { |
96 |
switch(level) {
|
|
97 |
case TRACE:
|
|
98 |
logger.trace(message, th);
|
|
99 |
break;
|
|
100 |
case ERROR:
|
|
101 |
logger.error(message, th);
|
|
102 |
break;
|
|
103 |
case WARN:
|
|
104 |
logger.warn(message, th);
|
|
105 |
break;
|
|
106 |
default:
|
|
107 |
case INFO:
|
|
108 |
logger.info(message, th);
|
|
109 |
break;
|
|
105 |
switch( level ) {
|
|
106 |
case TRACE: |
|
107 |
logger.trace(message, th); |
|
108 |
break; |
|
109 |
case ERROR: |
|
110 |
logger.error(message, th); |
|
111 |
break; |
|
112 |
case WARN: |
|
113 |
logger.warn(message, th); |
|
114 |
break; |
|
115 |
default: |
|
116 |
case INFO: |
|
117 |
logger.info(message, th); |
|
118 |
break; |
|
110 | 119 |
} |
111 | 120 |
} |
112 |
|
|
121 |
|
|
113 | 122 |
@Override |
114 |
public PluginServices getPlugin() {
|
|
123 |
public PluginServices getPlugin() { |
|
115 | 124 |
if( this.plugin == null ) { |
116 | 125 |
this.plugin = PluginsLocator.getManager().getPlugin(ScriptingExtension.class); |
117 | 126 |
} |
118 | 127 |
return this.plugin; |
119 | 128 |
} |
120 |
|
|
129 |
|
|
121 | 130 |
@Override |
122 | 131 |
public void execute(String actionCommand) { |
123 | 132 |
this.execute(actionCommand, null); |
... | ... | |
128 | 137 |
ScriptingUIManager uimanager = ScriptingSwingLocator.getUIManager(); |
129 | 138 |
WindowManager winmanager = ToolsSwingLocator.getWindowManager(); |
130 | 139 |
|
131 |
if ("tools-scripting-launcher".equalsIgnoreCase(command)) {
|
|
140 |
if( "tools-scripting-launcher".equalsIgnoreCase(command) ) {
|
|
132 | 141 |
winmanager.showWindow(uimanager.createLauncher().asJComponent(), uimanager.getTranslation("Scripting_Launcher"), WindowManager.MODE.TOOL); |
133 | 142 |
|
134 |
} else if ("tools-scripting-composer".equalsIgnoreCase(command)) { |
|
135 |
DynObject preferences = this.getPlugin().getPluginProperties(); |
|
136 |
Boolean composerUseHisWindowManager = (Boolean) preferences.getDynValue("ComposerUseHisWindowManager"); |
|
137 |
ScriptingUIManager uiManager = ScriptingSwingLocator.getUIManager(); |
|
138 |
if (composerUseHisWindowManager) { |
|
139 |
winmanager = new DefaultWindowManager(); |
|
140 |
uiManager.setWindowManager(winmanager); |
|
143 |
} else if( "tools-scripting-composer".equalsIgnoreCase(command) ) { |
|
144 |
if( composer_initialized ) { |
|
145 |
showScriptingComposer(); |
|
146 |
} else { |
|
147 |
Thread th = new Thread(new StartupAndShowScriptingComposer(), "StartupAndShowScriptingComposer"); |
|
148 |
th.start(); |
|
141 | 149 |
} |
142 |
JScriptingComposer composer = uimanager.createComposer(); |
|
143 |
uiManager.showWindow(composer.asJComponent(), uimanager.getTranslation("Scripting_Composer")); |
|
144 | 150 |
|
145 | 151 |
} else { |
146 | 152 |
ScriptingBaseScript script = uimanager.getManager().getScript(command); |
147 |
if (script != null) {
|
|
153 |
if( script != null ) {
|
|
148 | 154 |
script.run(args); |
149 | 155 |
} else { |
150 | 156 |
ApplicationManager application = ApplicationLocator.getManager(); |
... | ... | |
153 | 159 |
} |
154 | 160 |
} |
155 | 161 |
|
162 |
private void showScriptingComposer() { |
|
163 |
ScriptingUIManager uimanager = ScriptingSwingLocator.getUIManager(); |
|
164 |
WindowManager winmanager = ToolsSwingLocator.getWindowManager(); |
|
165 |
|
|
166 |
DynObject preferences = getPlugin().getPluginProperties(); |
|
167 |
Boolean composerUseHisWindowManager = (Boolean) preferences.getDynValue("ComposerUseHisWindowManager"); |
|
168 |
ScriptingUIManager uiManager = ScriptingSwingLocator.getUIManager(); |
|
169 |
if( composerUseHisWindowManager ) { |
|
170 |
winmanager = new DefaultWindowManager(); |
|
171 |
uiManager.setWindowManager(winmanager); |
|
172 |
} |
|
173 |
JScriptingComposer composer = uimanager.createComposer(); |
|
174 |
uiManager.showWindow( |
|
175 |
composer.asJComponent(), |
|
176 |
uimanager.getTranslation("Scripting_Composer") |
|
177 |
); |
|
178 |
} |
|
179 |
|
|
180 |
private class StartupAndShowScriptingComposer implements Runnable { |
|
181 |
|
|
182 |
@Override |
|
183 |
public void run() { |
|
184 |
SimpleTaskStatus status = ToolsLocator.getTaskStatusManager().createDefaultSimpleTaskStatus("Script composer startup"); |
|
185 |
status.setIndeterminate(); |
|
186 |
status.setAutoremove(true); |
|
187 |
status.add(); |
|
188 |
try { |
|
189 |
ScriptingManager manager = ScriptingLocator.getManager(); |
|
190 |
final List<ScriptingScript> autoruns = new ArrayList<>(); |
|
191 |
|
|
192 |
Visitor visitor = new Visitor() { |
|
193 |
|
|
194 |
@Override |
|
195 |
public void visit(Object o) throws VisitCanceledException, BaseException { |
|
196 |
ScriptingUnit unit = (ScriptingUnit) o; |
|
197 |
if( unit instanceof ScriptingScript && SCRIPT_COMPOSER_AUTORUN.equalsIgnoreCase(unit.getId()) ) { |
|
198 |
autoruns.add((ScriptingScript) unit); |
|
199 |
} |
|
200 |
} |
|
201 |
}; |
|
202 |
manager.getSystemFolder().accept(visitor); |
|
203 |
manager.getUserFolder().accept(visitor); |
|
204 |
if( !autoruns.isEmpty() ) { |
|
205 |
status.setRangeOfValues(0, autoruns.size()); |
|
206 |
int n=0; |
|
207 |
for (ScriptingScript autorun : autoruns) { |
|
208 |
status.setCurValue(n++); |
|
209 |
try { |
|
210 |
autorun.run(); |
|
211 |
} catch(Throwable th) { |
|
212 |
|
|
213 |
} |
|
214 |
} |
|
215 |
status.terminate(); |
|
216 |
|
|
217 |
} |
|
218 |
} catch(Throwable th) { |
|
219 |
|
|
220 |
} finally { |
|
221 |
status.terminate(); |
|
222 |
composer_initialized = true; |
|
223 |
} |
|
224 |
|
|
225 |
SwingUtilities.invokeLater(new Runnable() { |
|
226 |
@Override |
|
227 |
public void run() { |
|
228 |
showScriptingComposer(); |
|
229 |
} |
|
230 |
}); |
|
231 |
} |
|
232 |
} |
|
233 |
|
|
156 | 234 |
@Override |
157 | 235 |
public void initialize() { |
158 | 236 |
IconThemeHelper.registerIcon("action", "tools-scripting-launcher", this); |
159 | 237 |
IconThemeHelper.registerIcon("action", "tools-scripting-composer", this); |
160 | 238 |
IconThemeHelper.registerIcon("action", "tools-scripting-console-jython", this); |
161 |
|
|
239 |
|
|
162 | 240 |
initPaths(); |
163 | 241 |
Thread th = new Thread(new Runnable() { |
164 | 242 |
@Override |
... | ... | |
173 | 251 |
// Ignore. |
174 | 252 |
} |
175 | 253 |
} |
176 |
|
|
254 |
|
|
177 | 255 |
private void preloadPythonEngine() { |
178 | 256 |
ScriptingManager manager = (ScriptingManager) ScriptingLocator.getManager(); |
179 |
synchronized(manager) { |
|
257 |
synchronized (manager) {
|
|
180 | 258 |
String respath = "/scripting/langs/python/preload.py"; |
181 | 259 |
InputStream res = this.getClass().getResourceAsStream(respath); |
182 |
if (res != null) {
|
|
260 |
if( res != null ) {
|
|
183 | 261 |
logger.info("Scan for script engines"); |
184 | 262 |
List<String> lines; |
185 | 263 |
try { |
... | ... | |
187 | 265 |
String code = StringUtils.join(lines, "\n"); |
188 | 266 |
logger.info("Preload python script engine"); |
189 | 267 |
Script script = manager.createScript( |
190 |
"preload",
|
|
191 |
code,
|
|
192 |
ScriptingManager.PYTHON_LANGUAGE_NAME
|
|
268 |
"preload", |
|
269 |
code, |
|
270 |
ScriptingManager.PYTHON_LANGUAGE_NAME |
|
193 | 271 |
); |
194 | 272 |
logger.info("Preload python modules"); |
195 | 273 |
script.invokeFunction("main", null); |
196 |
|
|
274 |
|
|
197 | 275 |
} catch (Exception ex) { |
198 | 276 |
logger.warn("Can't run preload script for python.", ex); |
199 | 277 |
} |
... | ... | |
205 | 283 |
private void addLinkToPreviousVersion(ScriptingManager manager) { |
206 | 284 |
|
207 | 285 |
String contents = "[Unit]\n" |
208 |
+ "type = Folder\n"
|
|
209 |
+ "name = Previous version\n"
|
|
210 |
+ "description =\n"
|
|
211 |
+ "createdBy =\n"
|
|
212 |
+ "version =\n"
|
|
213 |
+ "\n"
|
|
214 |
+ "[Folder]\n"
|
|
215 |
+ "path = ../../org.gvsig.scripting.app.extension/scripts\n\n\n";
|
|
286 |
+ "type = Folder\n" |
|
287 |
+ "name = Previous version\n" |
|
288 |
+ "description =\n" |
|
289 |
+ "createdBy =\n" |
|
290 |
+ "version =\n" |
|
291 |
+ "\n" |
|
292 |
+ "[Folder]\n" |
|
293 |
+ "path = ../../org.gvsig.scripting.app.extension/scripts\n\n\n"; |
|
216 | 294 |
File previousVersion = new File(manager.getUserFolder().getFile(), "previous_version.inf"); |
217 |
if (!previousVersion.exists()) {
|
|
295 |
if( !previousVersion.exists() ) {
|
|
218 | 296 |
try { |
219 | 297 |
FileUtils.writeStringToFile(previousVersion, contents); |
220 | 298 |
} catch (IOException ex) { |
... | ... | |
233 | 311 |
this.addLinkToPreviousVersion(manager); |
234 | 312 |
|
235 | 313 |
List<File> pluginsFolders = new ArrayList<>(); |
236 |
for (File f : pluginManager.getPluginsFolders()) {
|
|
314 |
for( File f : pluginManager.getPluginsFolders() ) {
|
|
237 | 315 |
pluginsFolders.addAll(Arrays.asList(f.listFiles())); |
238 | 316 |
} |
239 | 317 |
|
240 |
for (File pluginFolder : pluginsFolders) {
|
|
318 |
for( File pluginFolder : pluginsFolders ) {
|
|
241 | 319 |
File scriptsFolder = new File(pluginFolder, "scripting/scripts"); |
242 |
if (scriptsFolder.exists()) {
|
|
320 |
if( scriptsFolder.exists() ) {
|
|
243 | 321 |
manager.registerSystemFolder(pluginFolder.getName(), scriptsFolder); |
244 | 322 |
} |
245 | 323 |
File libFolder = new File(pluginFolder, "scripting/lib"); |
246 |
if (libFolder.exists()) {
|
|
324 |
if( libFolder.exists() ) {
|
|
247 | 325 |
manager.addLibFolder(libFolder); |
248 | 326 |
} |
249 | 327 |
} |
250 | 328 |
manager.setPackagesFolder(pluginManager.getInstallFolder()); |
251 | 329 |
|
252 | 330 |
File localAddonRepositoryFolder = new File(manager.getRootUserFolder(), "addons"); |
253 |
if (!localAddonRepositoryFolder.exists()) {
|
|
331 |
if( !localAddonRepositoryFolder.exists() ) {
|
|
254 | 332 |
try { |
255 | 333 |
FileUtils.forceMkdir(localAddonRepositoryFolder); |
256 | 334 |
} catch (IOException ex) { |
257 | 335 |
logger.info("Can't create addons folder in '" + localAddonRepositoryFolder.getAbsolutePath() + "'.", ex); |
258 | 336 |
} |
259 |
}
|
|
337 |
} |
|
260 | 338 |
File initAddonFile = new File(localAddonRepositoryFolder, "__init__.py"); |
261 |
if (!initAddonFile.exists()) {
|
|
339 |
if( !initAddonFile.exists() ) {
|
|
262 | 340 |
try { |
263 | 341 |
FileUtils.touch(initAddonFile); |
264 | 342 |
} catch (IOException ex) { |
... | ... | |
266 | 344 |
} |
267 | 345 |
} |
268 | 346 |
} |
269 |
|
|
347 |
|
|
270 | 348 |
@Override |
271 | 349 |
public void postInitialize() { |
272 | 350 |
super.postInitialize(); |
273 | 351 |
PluginsManager pluginManager = PluginsLocator.getManager(); |
274 | 352 |
pluginManager.addStartupTask( |
275 |
"ExecuteAutorunScripts",
|
|
276 |
new ExecuteAutorunScriptsOnStartup(),
|
|
277 |
true,
|
|
278 |
600
|
|
353 |
"ExecuteAutorunScripts", |
|
354 |
new ExecuteAutorunScriptsOnStartup(), |
|
355 |
true, |
|
356 |
600 |
|
279 | 357 |
); |
280 |
|
|
358 |
|
|
281 | 359 |
Invocable initializer = new ScriptsInstallerInitializer(); |
282 | 360 |
initializer.call(this.getPlugin().getPluginName()); |
283 | 361 |
} |
... | ... | |
313 | 391 |
final I18nManager i18nManager = ToolsLocator.getI18nManager(); |
314 | 392 |
|
315 | 393 |
final List<ScriptingBaseScript> autoruns = new ArrayList<>(); |
316 |
|
|
394 |
|
|
317 | 395 |
try { |
318 | 396 |
List<File> pluginsFolders = new ArrayList<>(); |
319 |
for (File f : pluginManager.getPluginsFolders()) {
|
|
397 |
for( File f : pluginManager.getPluginsFolders() ) {
|
|
320 | 398 |
pluginsFolders.addAll(Arrays.asList(f.listFiles())); |
321 | 399 |
} |
322 | 400 |
|
323 | 401 |
application.message( |
324 |
i18nManager.getTranslation("_Searching_autorun_scripts_Xhorizontal_ellipsisX"),
|
|
325 |
JOptionPane.INFORMATION_MESSAGE
|
|
402 |
i18nManager.getTranslation("_Searching_autorun_scripts_Xhorizontal_ellipsisX"), |
|
403 |
JOptionPane.INFORMATION_MESSAGE |
|
326 | 404 |
); |
327 |
for (File pluginFolder : pluginsFolders) {
|
|
405 |
for( File pluginFolder : pluginsFolders ) {
|
|
328 | 406 |
File autorun_file = new File(pluginFolder, "scripting/scripts/autorun.inf"); |
329 |
if (autorun_file.exists()) {
|
|
407 |
if( autorun_file.exists() ) {
|
|
330 | 408 |
ScriptingBaseScript autorun = manager.getScript(autorun_file); |
331 |
if (autorun.isEnabled()) {
|
|
409 |
if( autorun.isEnabled() ) {
|
|
332 | 410 |
autoruns.add(autorun); |
333 | 411 |
} else { |
334 | 412 |
logger.info("Skip autorun script '" + autorun_file.getAbsolutePath() + "'."); |
... | ... | |
340 | 418 |
@Override |
341 | 419 |
public FileVisitResult visitFile(Path path, BasicFileAttributes bfa) throws IOException { |
342 | 420 |
File file = path.toFile(); |
343 |
if ("autorun.inf".equalsIgnoreCase(file.getName())) {
|
|
344 |
if (file.exists()) {
|
|
421 |
if( "autorun.inf".equalsIgnoreCase(file.getName()) ) {
|
|
422 |
if( file.exists() ) {
|
|
345 | 423 |
ScriptingBaseScript autorun = manager.getScript(file); |
346 | 424 |
if( autorun.isEnabled() ) { |
347 | 425 |
autoruns.add(autorun); |
... | ... | |
357 | 435 |
EnumSet<FileVisitOption> opts = EnumSet.of(FOLLOW_LINKS); |
358 | 436 |
Files.walkFileTree(Paths.get(manager.getRootUserFolder().toURI()), opts, Integer.MAX_VALUE, visitor); |
359 | 437 |
List<ScriptingFolder> folders = manager.getAlternativeUserFolders(); |
360 |
for (ScriptingFolder folder : folders) {
|
|
438 |
for( ScriptingFolder folder : folders ) {
|
|
361 | 439 |
Files.walkFileTree(Paths.get(folder.getFile().toURI()), opts, Integer.MAX_VALUE, visitor); |
362 | 440 |
} |
363 | 441 |
} catch (Exception ex) { |
364 | 442 |
logger.warn("Can't execute autoruns in home.", ex); |
365 | 443 |
} |
366 |
|
|
444 |
|
|
367 | 445 |
Collections.sort(autoruns, new Comparator<ScriptingBaseScript>() { |
368 | 446 |
|
369 | 447 |
@Override |
... | ... | |
371 | 449 |
return getScriptOrderKey(o2).compareToIgnoreCase(getScriptOrderKey(o1)); |
372 | 450 |
} |
373 | 451 |
}); |
374 |
|
|
375 |
for (ScriptingBaseScript autorun : autoruns) {
|
|
452 |
|
|
453 |
for( ScriptingBaseScript autorun : autoruns ) {
|
|
376 | 454 |
try { |
377 |
logger.info("running autorun script '" + autorun.getFile().getAbsolutePath() + "' ("+getScriptOrderKey(autorun)+", "+autorun.getIsolationGroup()+").");
|
|
455 |
logger.info("running autorun script '" + autorun.getFile().getAbsolutePath() + "' (" + getScriptOrderKey(autorun) + ", " + autorun.getIsolationGroup() + ").");
|
|
378 | 456 |
application.message( |
379 |
i18nManager.getTranslation( |
|
380 |
"_Running_autorun_script_from_XnameX", |
|
381 |
new String[]{autorun.getFile().getParentFile().getName()} |
|
382 |
), |
|
383 |
JOptionPane.INFORMATION_MESSAGE |
|
457 |
i18nManager.getTranslation( |
|
458 |
"_Running_autorun_script_from_XnameX", |
|
459 |
new String[]{ |
|
460 |
autorun.getFile().getParentFile().getName()} |
|
461 |
), |
|
462 |
JOptionPane.INFORMATION_MESSAGE |
|
384 | 463 |
); |
385 | 464 |
} catch (Exception ex) { |
386 | 465 |
// Ignore it |
... | ... | |
389 | 468 |
autorun.run(); |
390 | 469 |
} catch (Exception ex) { |
391 | 470 |
logger.warn("Can't execute autorun from '" + autorun.getFile().getAbsolutePath() + "'.", ex); |
392 |
}
|
|
471 |
} |
|
393 | 472 |
} |
394 |
|
|
473 |
|
|
395 | 474 |
} finally { |
396 | 475 |
logger.info("Running autorun scripts terminated."); |
397 | 476 |
application.message("", JOptionPane.INFORMATION_MESSAGE); |
... | ... | |
406 | 485 |
if( s != null ) { |
407 | 486 |
try { |
408 | 487 |
groupOrder = Integer.parseInt(s); |
409 |
} catch(Exception ex) { |
|
488 |
} catch (Exception ex) {
|
|
410 | 489 |
// Do nothing. |
411 | 490 |
} |
412 | 491 |
} |
413 | 492 |
s = o.getProperty("autorun.group.name"); |
414 |
if( s!=null ) {
|
|
493 |
if( s != null ) {
|
|
415 | 494 |
groupName = s; |
416 | 495 |
} |
417 | 496 |
s = o.getProperty("autorun.order"); |
418 | 497 |
if( s != null ) { |
419 | 498 |
try { |
420 | 499 |
scriptOrder = Integer.parseInt(s); |
421 |
} catch(Exception ex) { |
|
500 |
} catch (Exception ex) {
|
|
422 | 501 |
// Do nothing. |
423 | 502 |
} |
424 |
}
|
|
425 |
String key=MessageFormat.format(
|
|
426 |
"{0,number,000000}.{1}.{2,number,000000}",
|
|
427 |
groupOrder,
|
|
428 |
groupName,
|
|
429 |
scriptOrder
|
|
503 |
} |
|
504 |
String key = MessageFormat.format(
|
|
505 |
"{0,number,000000}.{1}.{2,number,000000}", |
|
506 |
groupOrder, |
|
507 |
groupName, |
|
508 |
scriptOrder |
|
430 | 509 |
); |
431 | 510 |
return key; |
432 | 511 |
} |
Also available in: Unified diff