• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Camelcade / Perl5-IDEA / #525521567

03 Jun 2025 07:42AM UTC coverage: 82.347% (+0.008%) from 82.339%
#525521567

push

github

hurricup
Localization Perl plugin

4 of 11 new or added lines in 5 files covered. (36.36%)

96 existing lines in 21 files now uncovered.

30852 of 37466 relevant lines covered (82.35%)

0.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.72
/plugin/core/src/main/java/com/perl5/lang/perl/idea/run/GenericPerlRunConfiguration.java
1
/*
2
 * Copyright 2015-2025 Alexandr Evstigneev
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 * http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package com.perl5.lang.perl.idea.run;
18

19
import com.intellij.execution.CommonProgramRunConfigurationParameters;
20
import com.intellij.execution.ExecutionException;
21
import com.intellij.execution.Executor;
22
import com.intellij.execution.configurations.*;
23
import com.intellij.execution.process.ProcessHandler;
24
import com.intellij.execution.runners.ExecutionEnvironment;
25
import com.intellij.execution.runners.RunConfigurationWithSuppressedDefaultRunAction;
26
import com.intellij.execution.ui.ConsoleView;
27
import com.intellij.openapi.application.ApplicationManager;
28
import com.intellij.openapi.application.ReadAction;
29
import com.intellij.openapi.diagnostic.Logger;
30
import com.intellij.openapi.project.Project;
31
import com.intellij.openapi.projectRoots.Sdk;
32
import com.intellij.openapi.projectRoots.impl.PerlSdkTable;
33
import com.intellij.openapi.util.InvalidDataException;
34
import com.intellij.openapi.util.WriteExternalException;
35
import com.intellij.openapi.util.text.StringUtil;
36
import com.intellij.openapi.vfs.VfsUtil;
37
import com.intellij.openapi.vfs.VirtualFile;
38
import com.intellij.util.Function;
39
import com.intellij.util.concurrency.annotations.RequiresReadLock;
40
import com.intellij.util.containers.ContainerUtil;
41
import com.intellij.util.execution.ParametersListUtil;
42
import com.intellij.util.net.NetUtils;
43
import com.intellij.util.xmlb.XmlSerializer;
44
import com.perl5.PerlBundle;
45
import com.perl5.lang.perl.idea.execution.PerlCommandLine;
46
import com.perl5.lang.perl.idea.execution.PerlTerminalExecutionConsole;
47
import com.perl5.lang.perl.idea.project.PerlProjectManager;
48
import com.perl5.lang.perl.idea.run.debugger.PerlDebugOptions;
49
import com.perl5.lang.perl.idea.sdk.host.PerlConsoleView;
50
import com.perl5.lang.perl.idea.sdk.host.PerlHostData;
51
import com.perl5.lang.perl.util.PerlFileUtil;
52
import com.perl5.lang.perl.util.PerlRunUtil;
53
import org.jdom.Element;
54
import org.jetbrains.annotations.Contract;
55
import org.jetbrains.annotations.NotNull;
56
import org.jetbrains.annotations.Nullable;
57

58
import java.io.File;
59
import java.nio.charset.Charset;
60
import java.nio.charset.UnsupportedCharsetException;
61
import java.util.*;
62
import java.util.stream.Collectors;
63

64
import static com.intellij.execution.configurations.GeneralCommandLine.ParentEnvironmentType.CONSOLE;
65
import static com.intellij.execution.configurations.GeneralCommandLine.ParentEnvironmentType.NONE;
66

67
public abstract class GenericPerlRunConfiguration extends LocatableConfigurationBase<LocatableRunConfigurationOptions>
68
  implements CommonProgramRunConfigurationParameters,
69
             RunConfigurationWithSuppressedDefaultRunAction,
70
             PerlDebugOptions {
71
  public static final Function<String, List<String>> FILES_PARSER = text -> StringUtil.split(text.trim(), "||");
1✔
72
  public static final Function<List<String>, String> FILES_JOINER = strings ->
1✔
73
    StringUtil.join(ContainerUtil.filter(strings, StringUtil::isNotEmpty), "||");
1✔
74
  public static final Function<String, List<String>> PREREQUISITES_PARSER = text -> Arrays.stream(text.trim().split("\\s*,\\s*"))
1✔
75
    .filter( StringUtil::isNotEmpty).sorted().toList() ;
1✔
76
  @SuppressWarnings("StaticMethodOnlyUsedInOneClass")
77
  public static final Function<List<String>, String> PREREQUISITES_JOINER = strings -> strings.stream()
1✔
78
    .filter(StringUtil::isNotEmpty).map(String::trim).sorted().collect(Collectors.joining(","));
1✔
79

80
  private static final Logger LOG = Logger.getInstance(GenericPerlRunConfiguration.class);
1✔
81

82
  private String myScriptPath;
83
  private String myScriptArguments;
84

85
  private String myPerlArguments = "";
1✔
86
  private String myWorkingDirectory;
87
  private Map<String, String> myEnvironments = new HashMap<>();
1✔
88
  private boolean myPassParentEnvironments = true;
1✔
89
  private String myConsoleCharset;
90
  private boolean myUseAlternativeSdk;
91
  private String myAlternativeSdkName;
92
  private @NotNull String myRequiredModules = "";
1✔
93

94
  // debugging-related options
95
  private String myScriptCharset = "utf8";
1✔
96
  private String myStartMode = "RUN";
1✔
97
  private boolean myIsNonInteractiveModeEnabled = false;
1✔
98
  private boolean myIsCompileTimeBreakpointsEnabled = false;
1✔
99
  private String myInitCode = "";
1✔
100

101
  private transient Integer myDebugPort;
102

103
  public GenericPerlRunConfiguration(@NotNull Project project, @NotNull ConfigurationFactory factory, @Nullable String name) {
104
    super(project, factory, name);
1✔
105
  }
1✔
106

107
  @Override
108
  public void readExternal(@NotNull Element element) throws InvalidDataException {
109
    super.readExternal(element);
×
110
    XmlSerializer.deserializeInto(this, element);
×
111
  }
×
112

113
  @Override
114
  public void writeExternal(@NotNull Element element) throws WriteExternalException {
115
    super.writeExternal(element);
1✔
116
    XmlSerializer.serializeInto(this, element);
1✔
117
  }
1✔
118

119
  @Override
120
  public final @Nullable PerlRunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment executionEnvironment)
121
    throws ExecutionException {
122
    var runner = executionEnvironment.getRunner();
1✔
123
    if (!(runner instanceof GenericPerlProgramRunner genericPerlProgramRunner)) {
1✔
124
      LOG.error("GenericPerlProgramRunner expected, got " + runner);
×
NEW
125
      throw new ExecutionException(
×
NEW
126
        PerlBundle.message("dialog.message.wrong.runner.used.to.run.perl.configuration.please.report.to.perl.plugin.developers"));
×
127
    }
128
    return genericPerlProgramRunner.createState(executionEnvironment);
1✔
129
  }
130

131
  /**
132
   * @return local path to the perl interpreter for this run configuration
133
   * @throws ExecutionException with human readable error message if interpreter path is empty
134
   */
135
  protected @NotNull String getEffectiveInterpreterPath() throws ExecutionException {
136
    Sdk effectiveSdk = getEffectiveSdk();
1✔
137
    String interpreterPath = PerlProjectManager.getInterpreterPath(effectiveSdk);
1✔
138
    if (StringUtil.isEmpty(interpreterPath)) {
1✔
139
      throw new ExecutionException(PerlBundle.message("perl.run.error.no.interpreter.path", effectiveSdk));
×
140
    }
141
    return interpreterPath;
1✔
142
  }
143

144
  public @NotNull Sdk getEffectiveSdk() throws ExecutionException {
145
    Sdk perlSdk;
146
    if (isUseAlternativeSdk()) {
1✔
147
      String alternativeSdkName = getAlternativeSdkName();
×
148
      if (StringUtil.isEmpty(alternativeSdkName)) {
×
149
        throw new ExecutionException(PerlBundle.message("perl.run.error.no.alternative.sdk.selected"));
×
150
      }
151
      perlSdk = PerlSdkTable.getInstance().findJdk(alternativeSdkName);
×
152
      if (perlSdk == null) {
×
153
        throw new ExecutionException(PerlBundle.message("perl.run.error.no.alternative.sdk", alternativeSdkName));
×
154
      }
155
      return perlSdk;
×
156
    }
157
    else {
158
      perlSdk = PerlProjectManager.getSdk(getProject());
1✔
159
      if (perlSdk == null) {
1✔
160
        throw new ExecutionException(PerlBundle.message("perl.run.error.no.sdk", getProject().getName()));
×
161
      }
162
    }
163
    return perlSdk;
1✔
164
  }
165

166
  @Override
167
  public String suggestedName() {
168
    List<VirtualFile> targetFiles = computeTargetFiles();
1✔
169
    return targetFiles.isEmpty() ? null : targetFiles.getFirst().getName();
1✔
170
  }
171

172
  protected @NotNull List<VirtualFile> computeTargetFiles() {
173
    return computeVirtualFilesFromPaths(getScriptPath());
1✔
174
  }
175

176
  private @NotNull VirtualFile computeNonNullScriptFile() throws ExecutionException {
177
    List<VirtualFile> targetFiles = computeTargetFiles();
1✔
178
    if (targetFiles.isEmpty()) {
1✔
179
      throw new ExecutionException(PerlBundle.message("perl.run.error.script.missing", getScriptPath()));
×
180
    }
181
    return targetFiles.getFirst();
1✔
182
  }
183

184
  public @NotNull String getRequiredModules() {
185
    return myRequiredModules;
1✔
186
  }
187

188
  public @NotNull List<String> getRequiredModulesList() {
189
    return PREREQUISITES_PARSER.fun(myRequiredModules);
1✔
190
  }
191

192
  public void setRequiredModules(@NotNull String requiredModules) {
193
    myRequiredModules = PREREQUISITES_JOINER.fun(PREREQUISITES_PARSER.fun(requiredModules));
1✔
194
  }
1✔
195

196
  public String getConsoleCharset() {
197
    return myConsoleCharset;
1✔
198
  }
199

200
  public void setConsoleCharset(String charset) {
201
    myConsoleCharset = charset;
1✔
202
  }
1✔
203

204
  public String getScriptPath() {
205
    return myScriptPath;
1✔
206
  }
207

208
  public void setScriptPath(String scriptPath) {
209
    myScriptPath = scriptPath;
1✔
210
  }
1✔
211

212
  public String getAlternativeSdkName() {
213
    return myAlternativeSdkName;
1✔
214
  }
215

216
  public void setAlternativeSdkName(String name) {
217
    myAlternativeSdkName = name;
1✔
218
  }
1✔
219

220
  public boolean isUseAlternativeSdk() {
221
    return myUseAlternativeSdk;
1✔
222
  }
223

224
  public void setUseAlternativeSdk(boolean value) {
225
    myUseAlternativeSdk = value;
1✔
226
  }
1✔
227

228
  @Override
229
  public @Nullable String getProgramParameters() {
230
    return myScriptArguments;
1✔
231
  }
232

233
  @Override
234
  public void setProgramParameters(@Nullable String s) {
235
    myScriptArguments = s;
1✔
236
  }
1✔
237

238
  @Override
239
  public @Nullable String getWorkingDirectory() {
240
    return myWorkingDirectory;
1✔
241
  }
242

243
  /**
244
   * @return virtual file for a working directory explicitly set by user
245
   */
246
  protected @Nullable VirtualFile computeExplicitWorkingDirectory() {
247
    String workingDirectory = getWorkingDirectory();
1✔
248
    if (StringUtil.isEmpty(workingDirectory)) {
1✔
249
      return null;
1✔
250
    }
251
    return VfsUtil.findFileByIoFile(new File(workingDirectory), true);
×
252
  }
253

254
  @Override
255
  public void setWorkingDirectory(@Nullable String s) {
256
    myWorkingDirectory = s;
1✔
257
  }
1✔
258

259
  @Override
260
  public @NotNull Map<String, String> getEnvs() {
261
    return myEnvironments;
1✔
262
  }
263

264
  @Override
265
  public void setEnvs(@NotNull Map<String, String> map) {
266
    myEnvironments = map;
1✔
267
  }
1✔
268

269
  @Override
270
  public boolean isPassParentEnvs() {
271
    return myPassParentEnvironments;
1✔
272
  }
273

274
  @Override
275
  public void setPassParentEnvs(boolean b) {
276
    myPassParentEnvironments = b;
1✔
277
  }
1✔
278

279
  public String getPerlArguments() {
280
    return myPerlArguments;
1✔
281
  }
282

283
  protected @NotNull List<String> getPerlArgumentsList() {
284
    String perlArguments = getPerlArguments();
1✔
285
    return StringUtil.isEmpty(perlArguments) ? Collections.emptyList() : ParametersListUtil.parse(perlArguments);
1✔
286
  }
287

288
  public void setPerlArguments(String perlArguments) {
289
    this.myPerlArguments = perlArguments;
1✔
290
  }
1✔
291

292
  @Override
293
  public String getStartMode() {
294
    return myStartMode;
1✔
295
  }
296

297
  @Override
298
  public void setStartMode(String startMode) {
299
    this.myStartMode = startMode;
1✔
300
  }
1✔
301

302
  @Override
303
  public String getPerlRole() {
304
    return PerlDebugOptions.ROLE_SERVER;
1✔
305
  }
306

307
  @Override
308
  public String getHostToBind() {
309
    return LOCAL_DEBUG_HOST;
1✔
310
  }
311

312
  @Override
313
  public int getDebugPort() throws ExecutionException {
314
    if (myDebugPort == null) {
1✔
315
      myDebugPort = NetUtils.tryToFindAvailableSocketPort();
1✔
316
      if (myDebugPort == -1) {
1✔
NEW
317
        throw new ExecutionException(PerlBundle.message("dialog.message.no.free.port.to.work.on"));
×
318
      }
319
    }
320

321
    return myDebugPort;
1✔
322
  }
323

324
  @Override
325
  public String getRemoteProjectRoot() {
326
    return getProject().getBasePath();
×
327
  }
328

329
  @Override
330
  public String getScriptCharset() {
331
    return myScriptCharset;
1✔
332
  }
333

334
  @Override
335
  public void setScriptCharset(String scriptCharset) {
336
    this.myScriptCharset = scriptCharset;
1✔
337
  }
1✔
338

339
  @Override
340
  public boolean isNonInteractiveModeEnabled() {
341
    return myIsNonInteractiveModeEnabled;
1✔
342
  }
343

344
  @Override
345
  public void setNonInteractiveModeEnabled(boolean nonInteractiveModeEnabled) {
346
    myIsNonInteractiveModeEnabled = nonInteractiveModeEnabled;
1✔
347
  }
1✔
348

349
  @Override
350
  public boolean isCompileTimeBreakpointsEnabled() {
351
    return myIsCompileTimeBreakpointsEnabled;
1✔
352
  }
353

354
  @Override
355
  public void setCompileTimeBreakpointsEnabled(boolean compileTimeBreakpointsEnabled) {
356
    myIsCompileTimeBreakpointsEnabled = compileTimeBreakpointsEnabled;
1✔
357
  }
1✔
358

359
  public @NotNull PerlCommandLine createCommandLine(@NotNull PerlRunProfileState perlRunProfileState) throws ExecutionException {
360
    PerlCommandLine commandLine = createBaseCommandLine(perlRunProfileState);
1✔
361
    commandLine.withParentEnvironmentType(isPassParentEnvs() ? CONSOLE : NONE);
1✔
362
    commandLine.withWorkDirectory(computeWorkingDirectory(perlRunProfileState.getEnvironment().getProject()));
1✔
363
    commandLine.withCharset(computeCharset());
1✔
364

365
    return commandLine.withPty(isUsePty());
1✔
366
  }
367

368
  protected boolean isUsePty() {
369
    return true;
1✔
370
  }
371

372
  protected @NotNull Charset computeCharset() throws ExecutionException {
373
    String charsetName = getConsoleCharset();
1✔
374
    if (!StringUtil.isEmpty(charsetName)) {
1✔
375
      try {
376
        return Charset.forName(charsetName);
1✔
377
      }
378
      catch (UnsupportedCharsetException e) {
×
379
        throw new ExecutionException(PerlBundle.message("perl.run.error.unknown.charset", charsetName));
×
380
      }
381
    }
382
    else {
383
      return computeNonNullScriptFile().getCharset();
×
384
    }
385
  }
386

387
  /**
388
   * @see PerlRemoteDebuggingRunProfileState#computeLocalProjectPath(PerlRemoteDebuggingConfiguration)
389
   */
390
  protected @Nullable String computeWorkingDirectory(@NotNull Project project) throws ExecutionException {
391
    String workDirectory = getWorkingDirectory();
1✔
392
    if (StringUtil.isNotEmpty(workDirectory)) {
1✔
393
      return workDirectory;
×
394
    }
395
    return ReadAction.compute(() -> computeWorkingDirectory(project, computeNonNullScriptFile()));
1✔
396
  }
397

398
  protected @NotNull PerlCommandLine createBaseCommandLine(@NotNull PerlRunProfileState perlRunProfileState) throws ExecutionException {
399
    ExecutionEnvironment executionEnvironment = perlRunProfileState.getEnvironment();
1✔
400
    Project project = executionEnvironment.getProject();
1✔
401
    List<String> additionalPerlArguments = perlRunProfileState.getAdditionalPerlArguments(this);
1✔
402
    Map<String, String> additionalEnvironmentVariables = perlRunProfileState.getAdditionalEnvironmentVariables();
1✔
403

404
    PerlCommandLine commandLine = PerlRunUtil.getPerlCommandLine(
1✔
405
      project, getEffectiveSdk(), computeNonNullScriptFile(), ContainerUtil.concat(getPerlArgumentsList(), additionalPerlArguments),
1✔
406
      getScriptArguments());
1✔
407

408
    if (commandLine == null) {
1✔
409
      throw new ExecutionException(PerlBundle.message("perl.run.error.sdk.corrupted", getEffectiveSdk()));
×
410
    }
411

412
    Map<String, String> environment = new HashMap<>(getEnvs());
1✔
413
    environment.putAll(additionalEnvironmentVariables);
1✔
414
    commandLine.withEnvironment(environment);
1✔
415
    return commandLine;
1✔
416
  }
417

418
  @RequiresReadLock
419
  protected static @Nullable String computeWorkingDirectory(@NotNull Project project, @NotNull VirtualFile virtualFile) {
420
    var fileContentRoot = PerlFileUtil.getContentRoot(project, virtualFile);
1✔
421
    if (fileContentRoot != null) {
1✔
422
      return fileContentRoot.getPath();
1✔
423
    }
424
    return project.getBasePath();
×
425
  }
426

427
  protected @NotNull List<String> getScriptArguments() {
428
    String programArguments = getProgramParameters();
1✔
429
    return StringUtil.isEmpty(programArguments) ? Collections.emptyList() : ParametersListUtil.parse(programArguments);
1✔
430
  }
431

432
  @Override
433
  public String getInitCode() {
434
    return myInitCode;
1✔
435
  }
436

437
  @Override
438
  public void setInitCode(String initCode) {
439
    this.myInitCode = initCode;
1✔
440
  }
1✔
441

442
  /**
443
   * @return list of VirtualFiles pointed by  {@code paths} joined with pipe. Reverse of {@link #computePathsFromVirtualFiles(List)}
444
   */
445
  public static @NotNull List<VirtualFile> computeVirtualFilesFromPaths(@Nullable String paths) {
446
    if (StringUtil.isEmpty(paths)) {
1✔
447
      return Collections.emptyList();
×
448
    }
449
    List<String> pathNames = FILES_PARSER.fun(paths);
1✔
450
    if (pathNames.isEmpty()) {
1✔
451
      return Collections.emptyList();
×
452
    }
453
    List<VirtualFile> virtualFiles = new ArrayList<>(pathNames.size());
1✔
454
    for (String pathName : pathNames) {
1✔
455
      if (StringUtil.isEmpty(pathName)) {
1✔
456
        continue;
×
457
      }
458
      ContainerUtil.addIfNotNull(virtualFiles, VfsUtil.findFileByIoFile(new File(pathName), false));
1✔
459
    }
1✔
460
    return virtualFiles;
1✔
461
  }
462

463
  /**
464
   * @return paths of {@code virtualFiles} joined with pipe, reverse of {@link #computeVirtualFilesFromPaths(String)}
465
   */
466
  public static @NotNull String computePathsFromVirtualFiles(@NotNull List<? extends VirtualFile> virtualFiles) {
467
    return FILES_JOINER.fun(ContainerUtil.map(virtualFiles, VirtualFile::getPath));
1✔
468
  }
469

470
  public @NotNull ConsoleView createConsole(@NotNull PerlRunProfileState runProfileState) throws ExecutionException {
471
    ExecutionEnvironment executionEnvironment = runProfileState.getEnvironment();
1✔
472
    PerlConsoleView console = ApplicationManager.getApplication().isUnitTestMode() ?
1✔
473
                              new PerlRunConsole(runProfileState.getEnvironment().getProject()) :
1✔
474
                              new PerlTerminalExecutionConsole(executionEnvironment.getProject());
1✔
475
    return console.withHostData(PerlHostData.from(getEffectiveSdk()));
1✔
476
  }
477

478
  /**
479
   * May patch {@code processHandler} created with {@code runProfileState} from current run configuration.
480
   *
481
   * @return patched process handler
482
   */
483
  protected @NotNull ProcessHandler doPatchProcessHandler(@NotNull ProcessHandler processHandler) {
484
    return processHandler;
1✔
485
  }
486

487
  /**
488
   * Method checks if specified script(s) path is ok.
489
   *
490
   * @throws RuntimeConfigurationException with human-readable error message
491
   */
492
  protected void checkConfigurationScriptPath() throws RuntimeConfigurationException {
493
    if (StringUtil.isEmptyOrSpaces(getScriptPath())) {
1✔
494
      throw new RuntimeConfigurationException(PerlBundle.message("perl.run.error.no.script.set"));
×
495
    }
496
    if (computeTargetFiles().isEmpty()) {
1✔
497
      throw new RuntimeConfigurationException(PerlBundle.message("perl.run.error.no.script.found", getScriptPath()));
×
498
    }
499
  }
1✔
500

501
  @Override
502
  public void checkConfiguration() throws RuntimeConfigurationException {
503
    checkConfigurationScriptPath();
1✔
504
    try {
505
      getEffectiveInterpreterPath();
1✔
506
    }
507
    catch (ExecutionException e) {
×
508
      throw new RuntimeConfigurationException(e.getMessage());
×
509
    }
1✔
510
  }
1✔
511

512
  /**
513
   * If profileState was build with {@link GenericPerlRunConfiguration}, patches {@code processHandler} using
514
   * {@link #doPatchProcessHandler(ProcessHandler)}
515
   *
516
   * @return patched process handler
517
   */
518
  @Contract("null,_->null; !null,_->!null")
519
  static @Nullable ProcessHandler patchProcessHandler(@Nullable ProcessHandler processHandler,
520
                                                      @NotNull PerlRunProfileState runProfileState) {
521
    if (processHandler == null) {
1✔
522
      return null;
×
523
    }
524
    RunProfile runConfiguration = runProfileState.getEnvironment().getRunProfile();
1✔
525
    return runConfiguration instanceof GenericPerlRunConfiguration perlRunConfiguration ?
1✔
526
      perlRunConfiguration.doPatchProcessHandler(processHandler) :
1✔
527
           processHandler;
×
528
  }
529
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc