/* * [Tabout.java] * * Summary: Convert Tabs to Spaces. * * Copyright: (c) 1998-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 1998-07-08 original. */ package com.mindprod.tabstospaces; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.EOFException; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.util.Random; /** * Convert Tabs to Spaces. *
* Expands tabs and converts line ending between Unix and NT conventions. * Trims the trailing spaces. * usage: java Tabout MySource.java /U * or java Tabout Mysource.java /N (the default) * /U = unix [\n] or /N = NT [\r\n] line ending conventions. * alternatives -u -U for unix, -n -N -w -W for windows * * @author Roedy Green, Canadian Mind Products * @version 1.0 1998-07-08 original.. * @since 1998-07-08 */ public class Tabout { /** * todo: * single char from console, properly. Need modal dialog, ActionListener, KeyListener. * - allow parms in any order * - allow "" as legit extension * - allow multiple filenames on the command line. * - use system default line ending. */ // how many spaces per tab stop. static final int TABSPACING = 8; /** * extensions known safe to run Tabout on. */ static final String[] goodExtensions = { "ans", "asm", "bat", "batfrag", "btm", "btmfrag", "c", "cfrag", "cmd", "cpp", "cppfrag", "ctl", "h", "hfrag", "hpp", "hppfrag", "htm", "html", "htmlfrag", "ih", "ini", "java", "javafrag", "key", "lst", "mac", "pas", "rh", "sql", "sqlfrag", "txt", "use", "xml", "xmlfrag", }; /** * extensions known unsafe to run Tabout on. */ static final String[] badExtensions = { "bmp", "class", "com", "dll", "exe", "gif", "jar", "jpg", "obj", "png", "ser", "so", "vep", "zip" }; private static final String EmbeddedCopyright = "Copyright: (c) 1998-2017 Roedy Green, Canadian Mind Products, http://mindprod.com"; // input "before" file static String inFilename; static File inFile; static BufferedReader inReader; // output "after" file, the temporary, later renamed to match the input static String outFilename; static File outFile; static PrintWriter outWriter; // which line end convention do we use static boolean unix = false; /** * analyse the command line. It should have a filename and optionally /U or /D * case-insensitive. */ static void analyseCommandLine( String[] args ) { if ( !( 1 <= args.length && args.length <= 2 ) ) { banner(); System.out.println( "Oops! usage: TABOUT Myfile.TXT /U \n" ); die(); } inFilename = args[ 0 ]; /* file to convert */ unix = false; if ( args.length == 2 ) { String option = args[ 1 ]; if ( option.startsWith( "/" ) || option.startsWith( "-" ) ) { option = option.substring( 1 ); } option = option.toLowerCase(); switch ( option ) { case "u": unix = true; break; case "n": case "w": case "d": unix = false; break; default: banner(); System.out.println( "Oops! usage: TABOUT Myfile.TXT /u (/w /d /n) \n" ); die(); } } } // end analyseCommandLine /** * display a banner about the author */ static void banner() { /* Usually not displayed, just embedded. */ System.out.println( "\n���� Tabout 2.3 ۲��" + "\nJava Freeware to expand tabs to spaces, and convert Windows <-> Unix files" + "\nCopyright: (c) 1998-2017 Roedy Green, Canadian Mind Products" + "\n#101 - 2536 Wark Street, Victoria, BC Canada V8T 4G8" + "\nTelephone: (250) 361-9093 Internet:roedyg@mindprod.com" + "\nMay be used freely for non-military use only\n\n" ); } // end banner /** * Ask user to confirm that some action is ok. * * @param prompt. Question to ask the user. * * @return true if the user answers, yes it is ok to proceed. * * Should redo this with a modal dialog so don't have to hit Y enter. */ static boolean confirm( String prompt ) { /* just give a warning */ System.out.print( prompt ); System.out.println( " (Y)es (N)o " ); while ( true ) { /* loop forever till user enters Y or N */ honk(); int response = '\033'; try { // read single keystroke, even though user has to hit enter. response = System.in.read(); // the console is a fileInputReader } catch ( IOException e ) { } response = Character.toUpperCase( ( char ) response ); switch ( response ) { case 'Y': System.out.println( "Yes" ); return true; case 'N': System.out.println( "No" ); return false; /* others, keep looping */ } // end switch } // end while } // end confirm /** * abort the run, clean up as best as possible. */ static void die() { honk(); try { if ( inReader != null ) { inReader.close(); } if ( outWriter != null ) { outWriter.close(); } } catch ( IOException e ) { } System.exit( 1 ); /* exit with errorlevel = 1 */ } // end die /** * make sure the filename we are about to process has a safe extension. */ static void ensureSafeFilename() { /* Ensure appropriate file name extensions. good =.ASM .PAS .etc - done without prompt bad =.EXE .COM .OBJ - abort warning =.DOC & others - ask */ String extension = ""; int whereDot = inFilename.lastIndexOf( '.' ); if ( whereDot >= 0 && whereDot <= inFilename.length() - 2 ) { extension = inFilename.substring( whereDot + 1 ); } for ( int i = 0; i < goodExtensions.length; i++ ) { if ( extension.equalsIgnoreCase( goodExtensions[ i ] ) ) { /* match, it is Good */ return; } } for ( int i = 0; i < badExtensions.length; i++ ) { if ( extension.equalsIgnoreCase( badExtensions[ i ] ) ) { /* match, it is bad */ banner(); System.out.print( "Oops! Tabout cannot be used on files with extensions such as " ); System.out.println( inFilename ); die(); } } /* just give a warning */ if ( !confirm( "Warning!\n" + "Tabout is not usually used on files such as " + inFilename + ".\n" + "Do you want to continue and expand the tabs anyway?" ) ) { die(); } } // end ensureSafeFilename /** * NO LONGER NEEDED. Use Java builtin instead. * Create a uniquely named temporary file of the form XXX99999999.tmp. * * @param id a string prepended on the file generated. Should you fail to delete * it later, the id will help identify where it came from. null and "" also allowed. * @param near if null, the temporary file will be created in the current directory. * If near represents a file, the temporary file will be created in the * same directory as near. * If near represents a directory, the temporary file will be created in that * directory. * If near is invalid, then the temporary file will be created in the current * directory * * @return a temporary File with a unique name of the form XXX99999999.tmp. */ static File getTempFile( String id, File near ) throws IOException { String prepend = ( id != null ) ? id : ""; String path = null; if ( near != null ) { if ( near.isFile() ) { path = near.getParent(); } else if ( near.isDirectory() ) { path = near.getPath(); } } Random wheel = new Random(); // seeded from the clock File tempFile = null; do { // generate random a number 10,000,000 .. 99,999,999 int unique = ( wheel.nextInt() & Integer.MAX_VALUE ) % 90000000 + 10000000; tempFile = new File( path, prepend + Integer.toString( unique ) + ".tmp" ); } while ( tempFile.exists() ); // We "finally" found a name not already used. Nearly always the first time. // Quickly stake our claim to it by opening/closing it to create it. // In theory somebody could have grabbed it in that tiny window since // we checked if it exists, but that is highly unlikely. new FileOutputStream( tempFile ).close(); // DEBUGGING peek at the name generated. if ( false ) { System.out.println( tempFile.getCanonicalPath() ); } return tempFile; } // end getTempFile /** * make a noise */ static void honk() { java.awt.Toolkit.getDefaultToolkit().beep(); // could try System.out.print("\007"); System.out.flush(); } // end honk /** * open the input "before" file */ static void openInReader() { try { inFile = new File( inFilename ); if ( !inFile.exists() ) { banner(); System.out.print( "Oops! Cannot find file " ); System.out.println( inFilename ); die(); } if ( !inFile.canRead() ) { banner(); System.out.print( "Oops! no permission to read (i.e. examine) the file " ); System.out.println( inFilename ); die(); } if ( !inFile.canWrite() ) { banner(); System.out.print( "Oops! no permission to write (i.e. change) the file " ); System.out.println( inFilename ); die(); } inReader = new BufferedReader( new FileReader( inFile ), 4096 /* buffsize */ ); } catch ( FileNotFoundException e ) { banner(); System.out.print( "Oops! Cannot open file " ); System.out.println( inFilename ); die(); } } // end openInReader /** * open the output "after" file */ static void openOutWriter() { try { // get a temporary file in the same directory as inFile. outFile = getTempFile( "Tabout", inFile ); outWriter = new PrintWriter( new BufferedWriter( new FileWriter( outFile ), 4096 /* buffsize */ ), false /* auto flush */ ); } catch ( IOException e ) { System.out.println( "Oops! Cannot create the temporary work file\n" ); die(); } } // end OpenOutWriter /** * copy inReader to outWriter, processing tabs and line ends * Presume files already open. Does not close them. */ static void processFiles() throws IOException { int col = 0; /* 1-based column we have written */ /* 0 = no non-blank chars on line yet */ int pendingSpaces = 0; /* spaces we may later drop or emit */ try { while ( true ) { int c = inReader.read(); if ( c < 0 ) { throw new EOFException(); } switch ( c ) { case ' ': /* save up spaces, so trailing spaces can be dropped. */ ++pendingSpaces; break; case '\t': /* effectively expand tab to 1 to 8 spaces */ pendingSpaces += ( TABSPACING - ( ( col + pendingSpaces ) % TABSPACING ) ); break; case '\n': /* ignore pending/trailing spaces */ if ( !unix ) { outWriter.print( '\r' ); } outWriter.print( '\n' ); col = 0; pendingSpaces = 0; break; case '\r': /* dos has \r\n, unix just \n */ /* we just ignore them here and generate them as needed on \n. */ break; default: /* ordinary non-blank char */ /* squirt out any pent up spaces */ for ( int i = 0; i < pendingSpaces; i++ ) { outWriter.print( ' ' ); } col += pendingSpaces; pendingSpaces = 0; /* put out the char itself */ outWriter.print( ( char ) c ); col++; break; } /* end switch */ } /* end while */ } catch ( EOFException e ) { } } // end processFiles /** * Command line utility to expand tabs, * modify line ends to NT or Unix conventions, * and remove trailing blanks. */ public static void main( String[] args ) { try { analyseCommandLine( args ); openInReader(); /* Open input "before" file. */ /* Make sure file exists before */ /* song and dance about extension. */ ensureSafeFilename(); /* make sure filename has sane extension */ openOutWriter(); /* open output "after" file */ System.out.println( "Expanding tabs to spaces in " + inFilename + " for " + ( unix ? "Unix/Linux." : "NT/Windows/DOS." ) ); /* copy inReader to outWriter expanding tabs */ processFiles(); /* Rename output to input */ inReader.close(); outWriter.close(); inFile.delete(); outFile.renameTo( inFile ); // don't delete outFile, it has been renamed to a real file } catch ( IOException e ) { System.out.print( "Oops! IO failure. e.g. out of disk space. \n" ); die(); } } // end main } // end class Tabout