<?php
//
// +----------------------------------------------------------------------+
// | PHP version 4.0                                                      |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2001 The PHP Group                                |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.0 of the PHP license,       |
// | that is bundled with this package in the file LICENSE, and is        |
// | available at through the world-wide-web at                           |
// | http://www.php.net/license/2_02.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.               |
// +----------------------------------------------------------------------+
// | Authors:   Tomas V.V.Cox <cox@idecnet.com>                           |
// |                                                                      |
// +----------------------------------------------------------------------+
//
// $Id: System.php,v 1.1.1.1 2001/12/14 22:14:02 zarzycki Exp $
//

// TODO:
// - Test under Windows (help is really appreciated in this point)
// - Build strong tests
// - Error reporting (now shows standar php errors)
// - Write doc

require_once 'PEAR.php';
require_once 'Console/Getopt.php';

/**
* System offers cross plattform compatible system functions
*
* Static functions for different operations. Should work under
* Unix and Windows. The names and usage has been taken from its repectively
* GNU commands.
*
* Usage: System::rm('-r file1 dir1');
*
* ------------------- EXPERIMENTAL STATUS -------------------
*
* @package  System
* @author   Tomas V.V.Cox <cox@idecnet.com>
* @version  $Version$
* @access   public
*/
class System extends PEAR
{
    /**
    * returns the commandline arguments of a function
    *
    * @param    string  $argv           the commandline
    * @param    string  $short_options  the allowed option short-tags 
    * @param    string  $long_options   the allowed option long-tags
    * @return   array   the given options and there values
    * @access private
    */
    function _parseArgs($argv, $short_options, $long_options = null)
    {
        if (!is_array($argv)) {
            $argv = preg_split('/\s+/', $argv);
        }
        $options = Console_Getopt::getopt($argv, $short_options);
        return $options;
    }

    /**
    * Creates a nested array representing the structure of a directory
    *
    * System::_dirToStruct('dir1', 0) =>
    *   Array
    *    (
    *    [dirs] => Array
    *        (
    *            [0] => dir1
    *        )
    *
    *    [files] => Array
    *        (
    *            [0] => dir1/file2
    *            [1] => dir1/file3
    *        )
    *    )
    * @param    string  $sPath      Name of the directory
    * @param    integer $maxinst    max. deep of the lookup
    * @param    integer $aktinst    starting deep of the lookup
    * @return   array   the structure of the dir
    * @access   private
    */

    function _dirToStruct($sPath, $maxinst, $aktinst = 0)
    {
        $struct = array('dirs' => array(), 'files' => array());
        if (($dir = @opendir($sPath)) === false) {
            return $struct; // XXX could not open error
        }
        $struct['dirs'][] = $sPath; // XXX don't add if '.' or '..' ?
        $list = array();
        while ($file = readdir($dir)) {
            if ($file != '.' && $file != '..') {
                $list[] = $file;
            }
        }
        closedir($dir);
        sort($list);
        foreach($list as $val) {
            $path = $sPath . DIRECTORY_SEPARATOR . $val;
            if (is_dir($path)) {
                if ($aktinst < $maxinst || $maxinst == 0) {
                    $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1);
                    $struct = array_merge_recursive($tmp, $struct);
                }
            } else {
                $struct['files'][] = $path;
            }
        }
        return $struct;
    }

    /**
    * Creates a nested array representing the structure of a directory and files
    *
    * @param    array $files Array listing files and dirs
    * @return   array
    * @see System::_dirToStruct()
    */
    function _multipleToStruct($files)
    {
        $struct = array('dirs' => array(), 'files' => array());
        foreach($files as $file) {
            if (is_dir($file)) {
                $tmp = System::_dirToStruct($file, 0);
                $struct = array_merge_recursive($tmp, $struct);
            } else {
                $struct['files'][] = $file;
            }
        }
        return $struct;
    }

    /**
    * The rm command for removing files. 
    * Supports multiple files and dirs and also recursive deletes
    *
    * @param    string  $args   the arguments for rm
    * @return   mixed   PEAR_Error or true for success
    * @access   public    
    */
    function rm($args)
    {
        $opts = System::_parseArgs($args, 'rf'); // "f" do nothing but like it :-)
        if (PEAR::isError($opts)) {
            return $opts;
        }
        foreach($opts[0] as $opt) {
            if ($opt[0] == 'r') {
                $do_recursive = true;
            }
        }
        if (isset($do_recursive)) {
            $struct = System::_multipleToStruct($opts[1]);
            if (PEAR::isError($struct)) {
                return $struct;
            }
            foreach($struct['files'] as $file) {
                unlink($file); // XXXX Works under Windows?
            }
            foreach($struct['dirs'] as $dir) {
                rmdir($dir);
            }
        } else {
            foreach ($opts[1] as $file) {
                $delete = (is_dir($file)) ? 'rmdir' : 'unlink'; // XXXX Windows?
                $delete($file);
            }
        }
        return true;
    }

    /**
    * Make directories
    *
    * @param    string  $args    the name of the director(y|ies) to create
    * @return   mixed   PEAR_Error or true for success
    * @access   public    
    */
    function mkDir($args)
    {
        $opts = System::_parseArgs($args, 'pm:');
        if (PEAR::isError($opts)) {
            return $opts;
        }
        $mode = 0777; // default mode
        foreach($opts[0] as $opt) {
            if ($opt[0] == 'p') {
                $create_parents = true;
            } elseif($opt[0] == 'm') {
                $mode = $opt[1];
            }
        }
        if (isset($create_parents)) {
            foreach($opts[1] as $dir) {
                $dirstack = array();
                while (!@is_dir($dir) && $dir != DIRECTORY_SEPARATOR) {
                    array_unshift($dirstack, $dir);
                    $dir = dirname($dir);
                }
                while ($newdir = array_shift($dirstack)) {
                    if (!mkdir($newdir, $mode)) {
                        break; // XXX error
                    }
                }
            }
        } else {
            foreach($opts[1] as $dir) {
                if (!mkdir($dir, $mode)) {
                    continue; // XXX error
                }
            }
        }
        return true;
    }

    /**
    * Concatenate files
    *
    * Usage:
    * 1) $var = System::cat('sample.txt test.txt');
    * 2) System::cat('sample.txt test.txt > final.txt');
    * 3) System::cat('sample.txt test.txt >> final.txt');
    *
    * Note: as the class use fopen, urls should work also (test that)
    *
    * @param    string  $args   the arguments
    * @return   boolean true on success
    * @access   public
    */
    function &cat($args)
    {
        $ret = null;
        $files = array();
        if (!is_array($args)) {
            $args = preg_split('/\s+/', $args);
        }
        for($i=0; $i < count($args); $i++) {
            if ($args[$i] == '>') {
                $mode = 'wb';
                $outputfile = $args[$i+1];
                break;
            } elseif ($args[$i] == '>>') {
                $mode = 'ab+';
                $outputfile = $args[$i+1];
                break;
            } else {
                $files[] = $args[$i];
            }
        }
        if (isset($mode)) {
            if (!$outputfd = fopen($outputfile, $mode)) {
                return $this->raiseError("Could not open $outputfile");
            }
            $ret = true;
        }
        foreach($files as $file) {
            if (!$fd = fopen($file, 'r')) {
                return $this->raiseError("Could not open $file");
            }
            while(!feof($fd)) {
                $cont = fread($fd, 2048);
                if (isset($outputfd)) {
                    fwrite($outputfd, $cont);
                } else {
                    $ret .= $cont;
                }
            }
            fclose($fd);
        }
        if (@is_resource($outputfd)) {
            fclose($outputfd);
        }
        return $ret;
    }

}
?>