Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/main/java/org/perlonjava/ArgumentParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ public static CompilerOptions parseArguments(String[] args) {
case "-p":
parsedArgs.processAndPrint = true;
break;
case "-i":
// Handle the -i switch for in-place editing
parsedArgs.inPlaceEdit = true; // Set inPlaceEdit to true
if (i + 1 < args.length && !args[i + 1].startsWith("-")) {
parsedArgs.inPlaceExtension = args[i + 1];
i++;
} else {
parsedArgs.inPlaceExtension = ".bak"; // Default extension
}
break;
case "-h":
case "-?":
case "--help":
Expand Down Expand Up @@ -115,6 +125,7 @@ private static void printHelp() {
System.out.println(" -c Compiles the input code only.");
System.out.println(" -n Process input files without printing lines.");
System.out.println(" -p Process input files and print each line.");
System.out.println(" -i[extension] Edit files in-place (makes backup if extension supplied).");
System.out.println(" -Idirectory Specify @INC/#include directory (several -I's allowed)");
System.out.println(" -h, --help Displays this help message.");
}
Expand All @@ -134,6 +145,8 @@ private static void printHelp() {
* - compileOnly: If true, the compiler will compile the input but won't execute it.
* - code: The source code to be compiled.
* - fileName: The name of the file containing the source code, if any.
* - inPlaceExtension: The extension used for in-place editing backups.
* - inPlaceEdit: Indicates if in-place editing is enabled.
*/
public static class CompilerOptions implements Cloneable {
public boolean debugEnabled = false;
Expand All @@ -143,8 +156,10 @@ public static class CompilerOptions implements Cloneable {
public boolean compileOnly = false;
public boolean processOnly = false; // For -n
public boolean processAndPrint = false; // For -p
public boolean inPlaceEdit = false; // New field for in-place editing
public String code = null;
public String fileName = null;
public String inPlaceExtension = null; // For -i

// Initialize @ARGV
public RuntimeArray argumentList = GlobalContext.getGlobalArray("main::ARGV");
Expand Down Expand Up @@ -172,10 +187,11 @@ public String toString() {
" compileOnly=" + compileOnly + ",\n" +
" code='" + (code != null ? code : "null") + "',\n" +
" fileName='" + (fileName != null ? fileName : "null") + "',\n" +
" inPlaceExtension='" + (inPlaceExtension != null ? inPlaceExtension : "null") + "',\n" +
" inPlaceEdit=" + inPlaceEdit + ",\n" +
" argumentList=" + argumentList + ",\n" +
" inc=" + inc + "\n" +
"}";
}
}
}

87 changes: 76 additions & 11 deletions src/main/java/org/perlonjava/runtime/DiamondIO.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,56 @@
import static org.perlonjava.runtime.GlobalContext.*;
import static org.perlonjava.runtime.RuntimeScalarCache.scalarUndef;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
* The DiamondIO class manages reading from multiple input files,
* similar to Perl's diamond operator (<>).
* similar to Perl's diamond operator (<>). It also supports in-place
* editing with backup creation, akin to Perl's -i switch.
*/
public class DiamondIO {

// Static variable to hold the current file reader
static RuntimeIO currentReader;

// Static variable to hold the current file writer (for in-place editing)
static RuntimeIO currentWriter;

// Flag to indicate if the end of all files has been reached
static boolean eofReached = false;

// Flag to indicate if the reading process has started
static boolean readingStarted = false;

// Static field to store the in-place extension for the -i switch
static String inPlaceExtension = null;

// Path to the temporary file to be deleted on exit
static Path tempFilePath = null;

static {
// Register a shutdown hook to delete the temporary file
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (tempFilePath != null) {
try {
Files.deleteIfExists(tempFilePath);
} catch (IOException e) {
System.err.println("Error: Unable to delete temporary file " + tempFilePath);
}
}
}));
}

/**
* Reads a line from the current file. If the end of the file is reached,
* it attempts to open the next file. If all files are exhausted, it returns
* an undefined scalar.
*
* @param arg An unused parameter, kept for compatibility with other readline methods
* @param ctx The context in which the method is called (SCALAR or LIST)
* @return A RuntimeScalar representing the line read from the file, or an
* undefined scalar if EOF is reached for all files.
*/
Expand Down Expand Up @@ -70,16 +99,22 @@ public static RuntimeDataProvider readline(RuntimeScalar arg, int ctx) {

/**
* Opens the next file in the list and sets it as the current reader.
* Updates the global variables to reflect the current file being read.
* If in-place editing is enabled, it also sets up the writer for the
* output file. Updates the global variables to reflect the current file
* being read and written.
*
* @return true if a new file was successfully opened, false if no more files are available.
*/
private static boolean openNextFile() {
// Close the current reader if it exists
// Close the current reader and writer if they exist
if (currentReader != null) {
currentReader.close();
currentReader = null;
}
if (currentWriter != null) {
currentWriter.close();
currentWriter = null;
}

// Get the next file name from the global ARGV array
RuntimeScalar fileName = getGlobalArray("main::ARGV").shift();
Expand All @@ -89,16 +124,46 @@ private static boolean openNextFile() {
return false;
}

// Set the current filename in the global $main::ARGV variable
getGlobalVariable("main::ARGV").set(fileName);
String originalFileName = fileName.toString();
String backupFileName = null;

// Check if in-place editing is enabled
if (GlobalContext.getCompilerOptions().inPlaceEdit) {
String extension = GlobalContext.getCompilerOptions().inPlaceExtension;
if (extension == null || extension.isEmpty()) {
// Create a temporary file for the original file
try {
tempFilePath = Files.createTempFile("temp_", null);
Files.move(Paths.get(originalFileName), tempFilePath);
} catch (IOException e) {
System.err.println("Error: Unable to create temporary file for " + originalFileName);
return false;
}
} else if (extension.contains("*")) {
backupFileName = extension.replace("*", originalFileName);
} else {
backupFileName = originalFileName + extension;
}

// Open the file and set it as the current reader
currentReader = RuntimeIO.open(fileName.toString());
// Rename the original file to the backup file if needed
if (backupFileName != null) {
try {
Files.move(Paths.get(originalFileName), Paths.get(backupFileName));
} catch (IOException e) {
System.err.println("Error: Unable to create backup file " + backupFileName);
return false;
}
}
}

// Set the current handle in the global main::ARGV handle
// Open the renamed file for reading
currentReader = RuntimeIO.open(tempFilePath != null ? tempFilePath.toString() : (backupFileName != null ? backupFileName : originalFileName));
getGlobalIO("main::ARGV").set(currentReader);

// Return true if the current reader is successfully set
return currentReader != null;
// Open the original file for writing (this is the ARGVOUT equivalent)
currentWriter = RuntimeIO.open(originalFileName, ">");
getGlobalIO("main::ARGVOUT").set(currentWriter);

return currentReader != null && currentWriter != null;
}
}
}