Index: /FCKeditor/trunk/_dev/_getopt.php
===================================================================
--- /FCKeditor/trunk/_dev/_getopt.php	(revision 1411)
+++ /FCKeditor/trunk/_dev/_getopt.php	(revision 1411)
@@ -0,0 +1,291 @@
+<?php
+//Required by fixlineends.php
+/* vim: set expandtab tabstop=4 shiftwidth=4: */
+// +----------------------------------------------------------------------+
+// | PHP Version 5                                                        |
+// +----------------------------------------------------------------------+
+// | Copyright (c) 1997-2004 The PHP Group                                |
+// +----------------------------------------------------------------------+
+// | This source file is subject to version 3.0 of the PHP license,       |
+// | that is bundled with this package in the file LICENSE, and is        |
+// | available through the world-wide-web at the following url:           |
+// | http://www.php.net/license/3_0.txt.                                  |
+// | If you did not receive a copy of the PHP license and are unable to   |
+// | obtain it through the world-wide-web, please send a note to          |
+// | license@php.net so we can mail you a copy immediately.               |
+// +----------------------------------------------------------------------+
+// | Author: Andrei Zmievski <andrei@php.net>                             |
+// +----------------------------------------------------------------------+
+//
+// $Id: Getopt.php,v 1.4 2007/06/12 14:58:56 cellog Exp $
+
+require_once 'PEAR.php';
+
+/**
+ * Command-line options parsing class.
+ *
+ * @author Andrei Zmievski <andrei@php.net>
+ *
+ */
+class Console_Getopt {
+    /**
+     * Parses the command-line options.
+     *
+     * The first parameter to this function should be the list of command-line
+     * arguments without the leading reference to the running program.
+     *
+     * The second parameter is a string of allowed short options. Each of the
+     * option letters can be followed by a colon ':' to specify that the option
+     * requires an argument, or a double colon '::' to specify that the option
+     * takes an optional argument.
+     *
+     * The third argument is an optional array of allowed long options. The
+     * leading '--' should not be included in the option name. Options that
+     * require an argument should be followed by '=', and options that take an
+     * option argument should be followed by '=='.
+     *
+     * The return value is an array of two elements: the list of parsed
+     * options and the list of non-option command-line arguments. Each entry in
+     * the list of parsed options is a pair of elements - the first one
+     * specifies the option, and the second one specifies the option argument,
+     * if there was one.
+     *
+     * Long and short options can be mixed.
+     *
+     * Most of the semantics of this function are based on GNU getopt_long().
+     *
+     * @param array  $args           an array of command-line arguments
+     * @param string $short_options  specifies the list of allowed short options
+     * @param array  $long_options   specifies the list of allowed long options
+     *
+     * @return array two-element array containing the list of parsed options and
+     * the non-option arguments
+     *
+     * @access public
+     *
+     */
+    function getopt2($args, $short_options, $long_options = null)
+    {
+        return Console_Getopt::doGetopt(2, $args, $short_options, $long_options);
+    }
+
+    /**
+     * This function expects $args to start with the script name (POSIX-style).
+     * Preserved for backwards compatibility.
+     * @see getopt2()
+     */    
+    function getopt($args, $short_options, $long_options = null)
+    {
+        return Console_Getopt::doGetopt(1, $args, $short_options, $long_options);
+    }
+
+    /**
+     * The actual implementation of the argument parsing code.
+     */
+    function doGetopt($version, $args, $short_options, $long_options = null)
+    {
+        // in case you pass directly readPHPArgv() as the first arg
+        if (PEAR::isError($args)) {
+            return $args;
+        }
+        if (empty($args)) {
+            return array(array(), array());
+        }
+        $opts     = array();
+        $non_opts = array();
+
+        settype($args, 'array');
+
+        if ($long_options) {
+            sort($long_options);
+        }
+
+        /*
+         * Preserve backwards compatibility with callers that relied on
+         * erroneous POSIX fix.
+         */
+        if ($version < 2) {
+            if (isset($args[0]{0}) && $args[0]{0} != '-') {
+                array_shift($args);
+            }
+        }
+
+        reset($args);
+        while (list($i, $arg) = each($args)) {
+
+            /* The special element '--' means explicit end of
+               options. Treat the rest of the arguments as non-options
+               and end the loop. */
+            if ($arg == '--') {
+                $non_opts = array_merge($non_opts, array_slice($args, $i + 1));
+                break;
+            }
+
+            if ($arg{0} != '-' || (strlen($arg) > 1 && $arg{1} == '-' && !$long_options)) {
+                $non_opts = array_merge($non_opts, array_slice($args, $i));
+                break;
+            } elseif (strlen($arg) > 1 && $arg{1} == '-') {
+                $error = Console_Getopt::_parseLongOption(substr($arg, 2), $long_options, $opts, $args);
+                if (PEAR::isError($error))
+                    return $error;
+            } elseif ($arg == '-') {
+                // - is stdin
+                $non_opts = array_merge($non_opts, array_slice($args, $i));
+                break;
+            } else {
+                $error = Console_Getopt::_parseShortOption(substr($arg, 1), $short_options, $opts, $args);
+                if (PEAR::isError($error))
+                    return $error;
+            }
+        }
+
+        return array($opts, $non_opts);
+    }
+
+    /**
+     * @access private
+     *
+     */
+    function _parseShortOption($arg, $short_options, &$opts, &$args)
+    {
+        for ($i = 0; $i < strlen($arg); $i++) {
+            $opt = $arg{$i};
+            $opt_arg = null;
+
+            /* Try to find the short option in the specifier string. */
+            if (($spec = strstr($short_options, $opt)) === false || $arg{$i} == ':')
+            {
+                return PEAR::raiseError("Console_Getopt: unrecognized option -- $opt");
+            }
+
+            if (strlen($spec) > 1 && $spec{1} == ':') {
+                if (strlen($spec) > 2 && $spec{2} == ':') {
+                    if ($i + 1 < strlen($arg)) {
+                        /* Option takes an optional argument. Use the remainder of
+                           the arg string if there is anything left. */
+                        $opts[] = array($opt, substr($arg, $i + 1));
+                        break;
+                    }
+                } else {
+                    /* Option requires an argument. Use the remainder of the arg
+                       string if there is anything left. */
+                    if ($i + 1 < strlen($arg)) {
+                        $opts[] = array($opt,  substr($arg, $i + 1));
+                        break;
+                    } else if (list(, $opt_arg) = each($args)) {
+                        /* Else use the next argument. */;
+                        if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) {
+                            return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt");
+                        }
+                    } else {
+                        return PEAR::raiseError("Console_Getopt: option requires an argument -- $opt");
+                    }
+                }
+            }
+
+            $opts[] = array($opt, $opt_arg);
+        }
+    }
+
+    /**
+     * @access private
+     *
+     */
+    function _isShortOpt($arg)
+    {
+        return strlen($arg) == 2 && $arg[0] == '-' && preg_match('/[a-zA-Z]/', $arg[1]);
+    }
+
+    /**
+     * @access private
+     *
+     */
+    function _isLongOpt($arg)
+    {
+        return strlen($arg) > 2 && $arg[0] == '-' && $arg[1] == '-' &&
+            preg_match('/[a-zA-Z]+$/', substr($arg, 2));
+    }
+
+    /**
+     * @access private
+     *
+     */
+    function _parseLongOption($arg, $long_options, &$opts, &$args)
+    {
+        @list($opt, $opt_arg) = explode('=', $arg, 2);
+        $opt_len = strlen($opt);
+
+        for ($i = 0; $i < count($long_options); $i++) {
+            $long_opt  = $long_options[$i];
+            $opt_start = substr($long_opt, 0, $opt_len);
+            $long_opt_name = str_replace('=', '', $long_opt);
+
+            /* Option doesn't match. Go on to the next one. */
+            if ($long_opt_name != $opt) {
+                continue;
+            }
+
+            $opt_rest  = substr($long_opt, $opt_len);
+
+            /* Check that the options uniquely matches one of the allowed
+               options. */
+            if ($i + 1 < count($long_options)) {
+                $next_option_rest = substr($long_options[$i + 1], $opt_len);
+            } else {
+                $next_option_rest = '';
+            }
+            if ($opt_rest != '' && $opt{0} != '=' &&
+                $i + 1 < count($long_options) &&
+                $opt == substr($long_options[$i+1], 0, $opt_len) &&
+                $next_option_rest != '' &&
+                $next_option_rest{0} != '=') {
+                return PEAR::raiseError("Console_Getopt: option --$opt is ambiguous");
+            }
+
+            if (substr($long_opt, -1) == '=') {
+                if (substr($long_opt, -2) != '==') {
+                    /* Long option requires an argument.
+                       Take the next argument if one wasn't specified. */;
+                    if (!strlen($opt_arg) && !(list(, $opt_arg) = each($args))) {
+                        return PEAR::raiseError("Console_Getopt: option --$opt requires an argument");
+                    }
+                    if (Console_Getopt::_isShortOpt($opt_arg) || Console_Getopt::_isLongOpt($opt_arg)) {
+                        return PEAR::raiseError("Console_Getopt: option requires an argument --$opt");
+                    }
+                }
+            } else if ($opt_arg) {
+                return PEAR::raiseError("Console_Getopt: option --$opt doesn't allow an argument");
+            }
+
+            $opts[] = array('--' . $opt, $opt_arg);
+            return;
+        }
+
+        return PEAR::raiseError("Console_Getopt: unrecognized option --$opt");
+    }
+
+    /**
+    * Safely read the $argv PHP array across different PHP configurations.
+    * Will take care on register_globals and register_argc_argv ini directives
+    *
+    * @access public
+    * @return mixed the $argv PHP array or PEAR error if not registered
+    */
+    function readPHPArgv()
+    {
+        global $argv;
+        if (!is_array($argv)) {
+            if (!@is_array($_SERVER['argv'])) {
+                if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
+                    return PEAR::raiseError("Console_Getopt: Could not read cmd args (register_argc_argv=Off?)");
+                }
+                return $GLOBALS['HTTP_SERVER_VARS']['argv'];
+            }
+            return $_SERVER['argv'];
+        }
+        return $argv;
+    }
+
+}
+
+?>
Index: /FCKeditor/trunk/_dev/fixlineends.bat
===================================================================
--- /FCKeditor/trunk/_dev/fixlineends.bat	(revision 1411)
+++ /FCKeditor/trunk/_dev/fixlineends.bat	(revision 1411)
@@ -0,0 +1,2 @@
+@echo off
+php fixlineends.php --eofnewline --eofstripwhite ../
Index: /FCKeditor/trunk/_dev/fixlineends.php
===================================================================
--- /FCKeditor/trunk/_dev/fixlineends.php	(revision 1410)
+++ /FCKeditor/trunk/_dev/fixlineends.php	(revision 1411)
@@ -4,16 +4,5 @@
 //http://www.gophp5.org/ ;)
 
-error_reporting(E_ALL | E_STRICT);
-
-/**
- * Strip whitespace from the end of file
- *
- */
-define('EOF_STRIP_WHITESPACE', true);
-/**
- * Force new line character at the end of file
- *
- */
-define('EOF_NEW_LINE', true);
+error_reporting(E_ALL);
 
 /**
@@ -70,4 +59,66 @@
 
 /**
+ * Do not modify anything below
+ * use command line arguments to modify script's behaviour
+ */
+
+/**
+ * Strip whitespace from the end of file
+ * @var boolean $eofstripwhite
+ */
+$eofstripwhite = false;
+/**
+ * Force new line character at the end of file
+ * @var boolean $eofnewline
+ */
+$eofnewline = false;
+/**
+ * Windows only
+ * If set to true, archive files/folders are skipped
+ * @var boolean $noarchive
+ */
+$noarchive = false;
+/**
+ * Windows only
+ * If set to true, hidden files/folders are skipped
+ * @var boolean $nohidden
+ */
+$nohidden = false;
+/**
+ * Windows only
+ * If set to true, system files/folders are skipped
+ * @var boolean $nosystem
+ */
+$nosystem = false;
+/**
+ * If set to true, dot files are skipped
+ * @var boolean $nodotfiles
+ */
+$nodotfiles = false;
+/**
+ * How deep to recurse into subdirectories
+ * -1 to disable
+ *  0 to fix only current directory
+ * 
+ * @var integer $maxdepth
+ */
+$maxdepth = -1;
+/**
+ * If set, regex is used to exclude files
+ * Warning: preg_match format expected
+ * example:
+ * --excluderegex = "/(\.\w+)$/"
+ * 
+ * @var string $excluderegex
+ */
+$excluderegex = "";
+
+/**
+ * Set to true if script is launched in Windows
+ * @var boolean $windows
+ */
+$windows = (strtolower(substr(PHP_OS, 0, 3)) == "win");
+
+/**
  * Filter file list using regular expression (negative result)
  *
@@ -85,5 +136,4 @@
         return !preg_match($this->regex, $this->current());
     }
-
 }
 
@@ -104,5 +154,22 @@
         return preg_match($this->regex, $this->current());
     }
-
+}
+
+/**
+ * Filter file list by depth
+ *
+ */
+class DepthFilter extends FilterIterator
+{
+    protected $depth;
+    public function __construct(Iterator $it, $depth)
+    {
+        parent::__construct($it);
+        $this->depth=$depth;
+    }
+    public function accept()
+    {
+        return $this->getInnerIterator()->getDepth()<$this->depth;
+    }
 }
 
@@ -124,14 +191,8 @@
 
     $modified = false;
-
-    if ($contents === false) {
-        echo "\rERROR: couldn't open the " . $path . " file" . "\n";
-        return false;
-    }
-
     $new_content = "";
     $contents_len = sizeof($contents);
 
-    if (EOF_STRIP_WHITESPACE) {
+    if ($GLOBALS['eofstripwhite']) {
         $lines_processed=0;
         //iterate through lines, from the end of file
@@ -140,5 +201,5 @@
             $contents[$i] = rtrim($contents[$i]);
             if ($old_line !== $contents[$i]) {
-                if (!EOF_NEW_LINE || $old_line !== $contents[$i] . constant($nl) || $lines_processed>0) {
+                if (!$GLOBALS['eofnewline'] || $old_line !== $contents[$i] . constant($nl) || $lines_processed>0) {
                     $modified = true;
                 }
@@ -150,5 +211,5 @@
             }
             else {
-                if (EOF_NEW_LINE) {
+                if ($GLOBALS['eofnewline']) {
                     $contents[$i] .= constant($nl);
                     if ($old_line !== $contents[$i]) {
@@ -180,5 +241,5 @@
                             return false;
                         }
-                        elseif(!EOF_STRIP_WHITESPACE) {
+                        elseif(!$GLOBALS['eofstripwhite']) {
                             $line = $line . CRLF;
                             $modified = true;
@@ -199,5 +260,5 @@
                             return false;
                         }
-                        elseif(!EOF_STRIP_WHITESPACE) {
+                        elseif(!$GLOBALS['eofstripwhite']) {
                             $line = $line . CR;
                             $modified = true;
@@ -222,5 +283,5 @@
                             return false;
                         }
-                        elseif(!EOF_STRIP_WHITESPACE) {
+                        elseif(!$GLOBALS['eofstripwhite']) {
                             $line = $line . LF;
                             $modified = true;
@@ -277,5 +338,15 @@
     else {
         $dir = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path), true);
+
+        if ($GLOBALS['maxdepth'] > -1) {
+            $dir = new DepthFilter($dir, $GLOBALS['maxdepth']+1);
+        }
+
         $dir = new NegRegexFilter($dir, "/\/(\.svn|CVS)/");
+
+        if ($GLOBALS['excluderegex']) {
+            $dir = new NegRegexFilter($dir, $GLOBALS['excluderegex']);
+        }
+
         foreach (array('CRLF', 'LF', 'CR') as $nl) {
 
@@ -287,4 +358,32 @@
             foreach ($filtered_dir as $file) {
                 if (!is_dir($file)) {
+                    $basename = basename($file);
+
+                    //skip dot files
+                    if ($GLOBALS['nodotfiles']) {
+                        if (strpos($basename, ".") === 0) {
+                            continue;
+                        }
+                    }
+
+                    if ($GLOBALS['windows']) {
+                        $attribs = trim(substr(shell_exec("attrib " . $file), 0, 5));
+                        //skip archive files
+                        if ($GLOBALS['noarchive'] && false !== strpos($attribs, "A")) {
+                            print "\r ".$progressbar[$j++ % 4]. " ". str_pad(basename($file), 35, " ", STR_PAD_RIGHT)." SKIPPED";
+                            continue;
+                        }
+                        //skip hidden files
+                        if ($GLOBALS['nohidden'] && false !== strpos($attribs, "H")) {
+                            print "\r ".$progressbar[$j++ % 4]. " ". str_pad(basename($file), 35, " ", STR_PAD_RIGHT)." SKIPPED";
+                            continue;
+                        }
+                        //skip system files
+                        if ($GLOBALS['nosystem'] && false !== strpos($attribs, "S")) {
+                            print "\r ".$progressbar[$j++ % 4]. " ". str_pad(basename($file), 35, " ", STR_PAD_RIGHT)." SKIPPED";
+                            continue;
+                        }
+                    }
+
                     fixFile($file, $nl);
                     print "\r ".$progressbar[$j++ % 4]. " ". str_pad(basename($file), 35, " ", STR_PAD_RIGHT);
@@ -295,55 +394,113 @@
 }
 
-/**
- * Get input from console
- *
- * @param string $prompt
- * @param array $options
- * @param string $default
- * @return string
- */
-function getInput($prompt, $options = null, $default = null) {
-    $stdin = fopen('php://stdin', 'r');
-    if (!is_array($options)) {
-        $print_options = '';
-    } else {
-        $print_options = '(' . implode('/', $options) . ')';
-    }
-
-    if ($default == null) {
-        echo $prompt . " $print_options \n" . '> ';
-    } else {
-        echo $prompt . " $print_options \n" . "[$default] > ";
-    }
-    $result = fgets($stdin);
-
-    if ($result === false){
-        exit;
-    }
-    $result = trim($result);
-
-    if ($default != null && empty($result)) {
-        return $default;
-    } else {
-        return $result;
-    }
-}
-
-/**
- * Validate path entered by user
- *
- * @param string $result
- * @return string
- */
-function validatePath($result) {
-    if (empty($result)) {
-        die();
-    }
-    if (!is_dir($result) && !is_file($_SERVER['argv'][1])) {
-        //let's process single file is valid path is given
-        echo "Invalid directory: " . $result ."\n";
-        validatePath(getInput("Enter valid path:"));
-    }
-    return $result;
+function printHelp() {
+    $help = <<<HELP
+    
+SYNOPSIS
+       php fixlineends.php [options] PATH [PATH2...]
+       grep [options] [-e PATTERN | -f FILE] [FILE...]
+
+DESCRIPTION
+       Traverse recursively all the paths given and fix line endings 
+       in each file.
+
+OPTIONS
+       --eofnewline
+            force new line character at the end of file
+       
+       --eofstripwhite
+            strip whitespace from the end of file
+       
+       --excluderegex=regex
+            use regex to exclude files, preg_match() format expected
+            
+       --help
+            display this help and exit
+            
+       --noarchive
+            if set to true, archive files are skipped (Windows only)
+       
+       --nodotfiles
+            if set to true, dot files are skipped
+            
+       --nohidden
+            if set to true, hidden files are skipped (Windows only)
+
+       --nosystem
+            if set to true, system files are skipped (Windows only)
+            
+       --maxdepth
+            fix line ends only if file is N or fewer levels below 
+            the command line argument; Use --max-depth=0 to omit 
+            subdirectories
+            
+EXAMPLES
+            php fixlineends.php --eofstripwhite --eofnewline --maxdepth=1 
+                --nodotfiles --excluderegex=/\_private/ .
+       
+       This command fixes line endings in current directory and in 
+       subdirectories placed one level below. Dot files are skipped.
+       Paths that match "_private" are skipped. White chars are stripped
+       at the end of file. New line character is added to the end of file
+       (if required).
+       
+       
+HELP;
+    echo $help;
+}
+
+function translateCommandArgs($args) {
+    $paths = $args[1];
+
+    foreach ($paths as $path) {
+        if (!is_dir($path) && !is_file($path)) {
+            die("Entered path is invalid: " . $path);
+        }
+    }
+
+    foreach ($args[0] as $arg) {
+        if (!isset($arg[0])) {
+            continue;
+        }
+        switch ($arg[0]) {
+            case '--noarchive':
+                $GLOBALS['noarchive'] = true;
+                break;
+
+            case '--help':
+                printHelp();
+                break;
+
+            case '--maxdepth':
+                $GLOBALS['maxdepth'] = intval($arg[1]);
+                break;
+
+            case '--nohidden':
+                $GLOBALS['nohidden'] = true;
+                break;
+
+            case '--nosystem':
+                $GLOBALS['nosystem'] = true;
+                break;
+
+            case '--nodotfiles':
+                $GLOBALS['nodotfiles'] = true;
+                break;
+
+            case '--excluderegex':
+                $GLOBALS['excluderegex'] = $arg[1];
+                break;
+                
+            case '--eofnewline':
+                $GLOBALS['eofnewline'] = true;
+                break;
+
+            case '--eofstripwhite':
+                $GLOBALS['eofstripwhite'] = true;
+                break;
+        }
+    }
+
+    return $paths;
 }
 
@@ -371,4 +528,31 @@
 }
 
-$path = validatePath(getInput("Enter path to the directory containing files that should be fixed. \nPath to single file is also allowed."));
-fixPath($path);
+if ($_SERVER['argc']>1) {
+    include "_getopt.php";
+
+    if ($windows) {
+        $longoptions = array("eofstripwhite", "eofnewline", "help", "noarchive", "nohidden", "nosystem", "nodotfiles", "maxdepth=", "excluderegex=");
+    }
+    else {
+        $longoptions = array("eofstripwhite", "eofnewline", "help", "nodotfiles", "maxdepth=", "excluderegex=");
+    }
+
+    $con  = new Console_Getopt;
+    $args = $con->readPHPArgv();
+    $options = $con->getopt($args, null, $longoptions);
+
+    if (PEAR::isError($options)) {
+        die($options->getMessage());
+    }
+
+    $paths = translateCommandArgs($options);
+    foreach ($paths as $path) {
+        fixPath($path);
+    }
+}
+else {
+    printHelp();
+    die();
+}
+
+print "\rDone!".str_repeat(" ",40)."\n";
