1. 手上有些JS代码,因为某种原因,起代码中没有任何回车、制表符。在编辑器中打开时,显示的就是一行。而我实现想研究和学习这些写得比较漂亮的JS代码,
2. 可是好想没有哪个编辑器自带了JS格式化的功能。它们都可以给JS着色,但就是不能控制代码格式,真是遗憾。
3. 我是我去网上搜看有没有前人做过这个东东,发现有个JS写的JS代码格式化工具,相当不错,可以给代码格式化,还可以着色。只不过结果都是显示在网页上,复制下来的时候那些制表符都没有了,所以贴到编辑器中时,代码还是比较混乱;而且这个每次只能格式话一个文件,还得自己手动去Copy & paste;而且因为是JS写的,速度也受到了不小的限制;
基于以上原因,我准备自己些一个小工具来格式化我需要的代码:
Section II: 功能
1. 因为很多编辑器都有JS的着色功能,所一我需要的仅仅是一个格式化功能而已,那就是主要将那种故意将代码中的空白符去掉的的JS加上换行、制表符等等。
大概有一下几种情况:
1). add /n/n before "function";
2). add /n after ";" (Except "for", and ";" between " " which is part of a string in javascript. ) and /t before the next line;
3). add "/n" and "/t" after "{";
4). add "/n" and "/t" before "}".
2. 不仅可以格式话一个给定的文件,还可以格式化给定的一个文件夹里的所有的JS文件。这个就是通过传给Main函数的参数来实现的。参数可以如下:
1)格式化一个文件:
-f path/file.js
或者:
--file path/file.js
2) 格式化给定的一个文件夹里的所有的JS文件
-d path/directory
或者:
--directory path/directory
举例:要格式化/home/newweapon/js/ 这个文件夹中的所有JS文件,可一使用如下命令:
JsFormatter -d /home/newweapon/js
Section III 缺陷
目前写的这个程序还是一个比较脆弱的程序。有很多情况没有考虑到,我在注释中把我想到的需要提高的基本都提了一下。但是因为我写这个小玩意主要是想把我自己下的一些JS代码格式化一下,而这个对我来说已经够用了,再加上自己的水平有限,所以没有将它写得很完美。等以后有时间了,再对这个程序做一些更新。
Section IV 代码
/**
* cn.newweapon.JsFormatter
*/
package cn.newweapon;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
/**
* @author Newweapon @ ustc [Email: newweapon111 (at) gmail.com]
* @version 0.0.1
* @Created 20071015
*
* @TODO 1. user can specify the formatted file name;<br/>
* 2. User can located the formatted files to a specified folder.<br/>
* 3. If a file is formatted partly already. Delete all blank characters first, and then format the file.<br/>
*/
public class JsFormatter {
/* ============ constants begin =================== */
/** Usage */
public static String USAGE = " Usage: JsFormatter -d path/directory JsFormatter -f path/filename.js " ;
/** Type: Directory = "0"; File = "1" */
public interface Type {
/** Directory: 0 */
public static String DIRECTORY = " 0 " ;
/** File: 1 */
public static String FILE = " 1 " ;
}
/* ============ constants end =================== */
/**
* Entry point of the project.
*
* @param args Like "-d /path/directory" or "-f /path/file.js"
*/
public static void main(String[] args) {
String startMsg = " Processing... " ;
String finishMsg = " Finished. " ;
// Parameters check.
if (args.length != 2 ) {
System.err.println(USAGE);
return ;
}
// Get the two parameters.
String type = args[ 0 ];
String path = args[ 1 ];
// Parameters check.
if ( " -d " .equals(type) || " --directory " .equals(type)) {
type = Type.DIRECTORY;
} else if ( " -f " .equals(type) || " --file " .equals(type)) {
type = Type.FILE;
} else {
System.err.println(USAGE);
return ;
}
// Check file type
if (Type.FILE.equals(type)) {
if (path.length() <= 3 || ! isJsFile(path)) {
System.err.println( " The file must be a JS file. " );
return ;
}
}
// Start message
System.out.println(startMsg);
// Format file(s)
try {
if (Type.FILE.equals(type)) {
formatFile(path);
} else {
List < String > jsFileList = getJsFileList(path);
Iterator < String > it = jsFileList.iterator();
while (it.hasNext()) {
formatFile(it.next());
}
finishMsg += " ( " + jsFileList.size() + " file(s) formatted) " ;
}
} catch (Exception e) {
e.printStackTrace();
return ;
}
// Finish message
System.out.println(finishMsg);
}
/**
* Format a JS file.
*
* @param fileName The file name of the file which is to be formatted.
* @return String The formatted string.
* @throws IOException Exception when open, read and write file.
*/
private static void formatFile(String fileName) throws IOException {
String formattedFileName = fileName + " .formatted " ;
FileReader fr = new FileReader(fileName);
FileWriter fw = new FileWriter(formattedFileName);
String lastWord = "" ;
int forCount = 0 ;
int quoteCount = 0 ;
int sigleQuoteCount = 0 ;
int bracketCount = 0 ;
int thisChar = 0 ;
int lastChar = 0 ;
int nextChar = 0 ;
thisChar = fr.read();
if (thisChar != - 1 ) {
nextChar = fr.read();
}
while (thisChar != - 1 ) {
// find and replace
switch (thisChar) {
// 2. add after ";" (Except "for", and ";" between " " which is part of a string in javascript. ) and before the next line
case ' ; ' :
// If the ";" is in quote or in "for", then not print " "
if (quoteCount > 0 || sigleQuoteCount > 0 || forCount > 0 ) {
fw.write( ' ; ' );
if (forCount > 0 ) {
forCount -- ;
}
// Add " " after ";"
} else {
fw.write( ' ; ' );
if ( ' ' != nextChar && ' ' != nextChar) {
fw.write( ' ' );
fillTableChar(fw, bracketCount);
}
}
break ;
case ' { ' : // 3. add " " and " " after "{"
bracketCount ++ ;
fw.write( ' { ' );
if ( ' ' != nextChar && ' ' != nextChar) { // If the file is already formatted, don't add after {.
fw.write( ' ' );
fillTableChar(fw, bracketCount);
}
break ;
case ' } ' : // 4. add " " and " " before "}"
bracketCount -- ;
fw.write( ' ' );
fillTableChar(fw, bracketCount);
fw.write( ' } ' );
if ( ' ; ' != nextChar && ' } ' != nextChar && ' ' != nextChar && ' ' != nextChar) {
fw.write( ' ' );
fillTableChar(fw, bracketCount);
}
break ;
case ' ' ' :
fw.write( ' ' ' );
if (quoteCount == 0 ) { // When ' is not between "", change its state.
sigleQuoteCount = sigleQuoteCount == 0 ? 1 : 0 ;
}
break ;
case ' " ' :
fw.write( ' " ' );
if (sigleQuoteCount == 0 ) { // When ' is not between "", change its state.
quoteCount = quoteCount == 0 ? 1 : 0 ;
}
break ;
case ' f ' : // 1. add before "function"
if (nextChar == ' u ' && lastChar != ' = ' ) { // TODO This is a very weak way to determine whether this coming word is "function", so it is need to be fixed.
fw.write( ' ' );
fw.write( ' ' );
}
fw.write( ' f ' );
break ;
default :
fw.write(thisChar);
break ;
}
if (isAlpha(thisChar)) {
if ( ! isAlpha(lastChar)) {
lastWord = "" ;
}
lastWord += String.valueOf(thisChar);
} else {
if (isAlpha(lastChar)) {
if ( " 102111114 " .equals(lastWord)) { // "for"
forCount = 2 ;
}
// TODO Whether is is suitable here to determine "function" and add " " before it?
} else {
lastWord = String.valueOf(thisChar);
}
}
lastChar = thisChar;
thisChar = nextChar;
if (thisChar != - 1 ) {
nextChar = fr.read();
}
}
// close the files
fw.close();
fr.close();
}
/**
* Find all JS files in the specified directory.
*
* @param directory The directory in which the files to be listed.
* @return List<String> The JS file list.
*/
private static List < String > getJsFileList(String directory) {
List < String > jsFileList = new ArrayList < String > ();
list(directory, jsFileList);
return jsFileList;
}
/**
* List all the JS files in the specified directory recursively.
*
* @param path The path to be recursively searched for JS files.
* @param result The path and file list
*/
private static void list(String path, List < String > result) {
File f = new File(path);
if (f.isDirectory()) {
File[] fileList = f.listFiles();
for ( int i = 0 ; i < fileList.length; i ++ ) {
list(fileList[i].getPath(), result);
}
} else {
if (isJsFile(f.getName())) {
result.add(f.getPath());
}
}
}
/**
* Determine whether the the specified file is a JS file.
*
* @param fileName
* @return True: is a JS file; False: not a JS file.
*/
private static boolean isJsFile(String fileName) {
// TODO use pattern!!!
return " .js " .equals(fileName.substring(fileName.length() - 3 ));
}
/**
* List all JS files in the specified directory(Not in their sub-directory).
*
* @param dir The specified directory.
* @return String[] The JS file list.
*/
private static String[] getSingleDirJsFileList( final String dir) {
String[] jsFileList;
File path = new File(dir);
jsFileList = path.list( new FilenameFilter() {
private Pattern pattern = Pattern.compile( " .js " );
public boolean accept(File dir, String name) {
return pattern.matcher( new File(name).getName()).matches();
}
});
return jsFileList;
}
/**
* Check whether the character is an alpha char.
* <b><red>Actually, the words exist in a function name would not be limit among those we list below.
* This need to be fixed. </red></b>
*
* @param c The char to be checked.
* @return boolean True: is alpha char; False: is not alpha char.
*/
private static boolean isAlpha( int c) {
return ((c > ' a ' && c < ' z ' ) || (c > ' A ' && c < ' Z ' ) || (c > ' 0 ' && c < ' 9 ' ));
}
/**
* Fill specified number of ' '
*
* @param fw FileWriter
* @param charNum Specified number of ' '
* @throws IOException Exception when writing file
*/
private static void fillTableChar(FileWriter fw, int charNum) throws IOException {
for ( int i = 0 ; i < charNum; i ++ ) {
fw.write( ' ' );
}
}
}
* cn.newweapon.JsFormatter
*/
package cn.newweapon;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
/**
* @author Newweapon @ ustc [Email: newweapon111 (at) gmail.com]
* @version 0.0.1
* @Created 20071015
*
* @TODO 1. user can specify the formatted file name;<br/>
* 2. User can located the formatted files to a specified folder.<br/>
* 3. If a file is formatted partly already. Delete all blank characters first, and then format the file.<br/>
*/
public class JsFormatter {
/* ============ constants begin =================== */
/** Usage */
public static String USAGE = " Usage: JsFormatter -d path/directory JsFormatter -f path/filename.js " ;
/** Type: Directory = "0"; File = "1" */
public interface Type {
/** Directory: 0 */
public static String DIRECTORY = " 0 " ;
/** File: 1 */
public static String FILE = " 1 " ;
}
/* ============ constants end =================== */
/**
* Entry point of the project.
*
* @param args Like "-d /path/directory" or "-f /path/file.js"
*/
public static void main(String[] args) {
String startMsg = " Processing... " ;
String finishMsg = " Finished. " ;
// Parameters check.
if (args.length != 2 ) {
System.err.println(USAGE);
return ;
}
// Get the two parameters.
String type = args[ 0 ];
String path = args[ 1 ];
// Parameters check.
if ( " -d " .equals(type) || " --directory " .equals(type)) {
type = Type.DIRECTORY;
} else if ( " -f " .equals(type) || " --file " .equals(type)) {
type = Type.FILE;
} else {
System.err.println(USAGE);
return ;
}
// Check file type
if (Type.FILE.equals(type)) {
if (path.length() <= 3 || ! isJsFile(path)) {
System.err.println( " The file must be a JS file. " );
return ;
}
}
// Start message
System.out.println(startMsg);
// Format file(s)
try {
if (Type.FILE.equals(type)) {
formatFile(path);
} else {
List < String > jsFileList = getJsFileList(path);
Iterator < String > it = jsFileList.iterator();
while (it.hasNext()) {
formatFile(it.next());
}
finishMsg += " ( " + jsFileList.size() + " file(s) formatted) " ;
}
} catch (Exception e) {
e.printStackTrace();
return ;
}
// Finish message
System.out.println(finishMsg);
}
/**
* Format a JS file.
*
* @param fileName The file name of the file which is to be formatted.
* @return String The formatted string.
* @throws IOException Exception when open, read and write file.
*/
private static void formatFile(String fileName) throws IOException {
String formattedFileName = fileName + " .formatted " ;
FileReader fr = new FileReader(fileName);
FileWriter fw = new FileWriter(formattedFileName);
String lastWord = "" ;
int forCount = 0 ;
int quoteCount = 0 ;
int sigleQuoteCount = 0 ;
int bracketCount = 0 ;
int thisChar = 0 ;
int lastChar = 0 ;
int nextChar = 0 ;
thisChar = fr.read();
if (thisChar != - 1 ) {
nextChar = fr.read();
}
while (thisChar != - 1 ) {
// find and replace
switch (thisChar) {
// 2. add after ";" (Except "for", and ";" between " " which is part of a string in javascript. ) and before the next line
case ' ; ' :
// If the ";" is in quote or in "for", then not print " "
if (quoteCount > 0 || sigleQuoteCount > 0 || forCount > 0 ) {
fw.write( ' ; ' );
if (forCount > 0 ) {
forCount -- ;
}
// Add " " after ";"
} else {
fw.write( ' ; ' );
if ( ' ' != nextChar && ' ' != nextChar) {
fw.write( ' ' );
fillTableChar(fw, bracketCount);
}
}
break ;
case ' { ' : // 3. add " " and " " after "{"
bracketCount ++ ;
fw.write( ' { ' );
if ( ' ' != nextChar && ' ' != nextChar) { // If the file is already formatted, don't add after {.
fw.write( ' ' );
fillTableChar(fw, bracketCount);
}
break ;
case ' } ' : // 4. add " " and " " before "}"
bracketCount -- ;
fw.write( ' ' );
fillTableChar(fw, bracketCount);
fw.write( ' } ' );
if ( ' ; ' != nextChar && ' } ' != nextChar && ' ' != nextChar && ' ' != nextChar) {
fw.write( ' ' );
fillTableChar(fw, bracketCount);
}
break ;
case ' ' ' :
fw.write( ' ' ' );
if (quoteCount == 0 ) { // When ' is not between "", change its state.
sigleQuoteCount = sigleQuoteCount == 0 ? 1 : 0 ;
}
break ;
case ' " ' :
fw.write( ' " ' );
if (sigleQuoteCount == 0 ) { // When ' is not between "", change its state.
quoteCount = quoteCount == 0 ? 1 : 0 ;
}
break ;
case ' f ' : // 1. add before "function"
if (nextChar == ' u ' && lastChar != ' = ' ) { // TODO This is a very weak way to determine whether this coming word is "function", so it is need to be fixed.
fw.write( ' ' );
fw.write( ' ' );
}
fw.write( ' f ' );
break ;
default :
fw.write(thisChar);
break ;
}
if (isAlpha(thisChar)) {
if ( ! isAlpha(lastChar)) {
lastWord = "" ;
}
lastWord += String.valueOf(thisChar);
} else {
if (isAlpha(lastChar)) {
if ( " 102111114 " .equals(lastWord)) { // "for"
forCount = 2 ;
}
// TODO Whether is is suitable here to determine "function" and add " " before it?
} else {
lastWord = String.valueOf(thisChar);
}
}
lastChar = thisChar;
thisChar = nextChar;
if (thisChar != - 1 ) {
nextChar = fr.read();
}
}
// close the files
fw.close();
fr.close();
}
/**
* Find all JS files in the specified directory.
*
* @param directory The directory in which the files to be listed.
* @return List<String> The JS file list.
*/
private static List < String > getJsFileList(String directory) {
List < String > jsFileList = new ArrayList < String > ();
list(directory, jsFileList);
return jsFileList;
}
/**
* List all the JS files in the specified directory recursively.
*
* @param path The path to be recursively searched for JS files.
* @param result The path and file list
*/
private static void list(String path, List < String > result) {
File f = new File(path);
if (f.isDirectory()) {
File[] fileList = f.listFiles();
for ( int i = 0 ; i < fileList.length; i ++ ) {
list(fileList[i].getPath(), result);
}
} else {
if (isJsFile(f.getName())) {
result.add(f.getPath());
}
}
}
/**
* Determine whether the the specified file is a JS file.
*
* @param fileName
* @return True: is a JS file; False: not a JS file.
*/
private static boolean isJsFile(String fileName) {
// TODO use pattern!!!
return " .js " .equals(fileName.substring(fileName.length() - 3 ));
}
/**
* List all JS files in the specified directory(Not in their sub-directory).
*
* @param dir The specified directory.
* @return String[] The JS file list.
*/
private static String[] getSingleDirJsFileList( final String dir) {
String[] jsFileList;
File path = new File(dir);
jsFileList = path.list( new FilenameFilter() {
private Pattern pattern = Pattern.compile( " .js " );
public boolean accept(File dir, String name) {
return pattern.matcher( new File(name).getName()).matches();
}
});
return jsFileList;
}
/**
* Check whether the character is an alpha char.
* <b><red>Actually, the words exist in a function name would not be limit among those we list below.
* This need to be fixed. </red></b>
*
* @param c The char to be checked.
* @return boolean True: is alpha char; False: is not alpha char.
*/
private static boolean isAlpha( int c) {
return ((c > ' a ' && c < ' z ' ) || (c > ' A ' && c < ' Z ' ) || (c > ' 0 ' && c < ' 9 ' ));
}
/**
* Fill specified number of ' '
*
* @param fw FileWriter
* @param charNum Specified number of ' '
* @throws IOException Exception when writing file
*/
private static void fillTableChar(FileWriter fw, int charNum) throws IOException {
for ( int i = 0 ; i < charNum; i ++ ) {
fw.write( ' ' );
}
}
}