<commit_before><?php

namespace sndsgd\util;

use \InvalidArgumentException;
use \RecursiveDirectoryIterator as RDI;
use \RecursiveIteratorIterator as RII;


/**
 * Directory utility methods
 */
class Dir
{
   // bitmask values for use with sndsgd\Path::test()
   // @see sndsgd\Path for sub values
   const READABLE = 21;
   const WRITABLE = 13;
   const READABLE_WRITABLE = 29;

   /**
    * Verify if a dir is readable
    * 
    * @param string $path An absolute path to the dir to test
    * @return boolean|string
    * @return boolean:true The dir is readable
    * @return string A message describing the failure
    */
   public static function isReadable($path)
   {
      return Path::test($path, self::READABLE);
   }

   /**
    * Determine if a directory can be written to
    *
    * if the path does not exist, parent dirs will be analyzed
    * (the directory being tested does not have to exist)
    * @param string $path An absolute path to test
    * @return boolean|string
    * @return boolean:true The directory either exists, or can be created
    * @return string A message describing the failure
    */
   public static function isWritable($path)
   {
      while (file_exists($path) === false) {
         $path = dirname($path);
      }
      return Path::test($path, self::WRITABLE);
   }

   /**
    * Prepare a directory for writing
    * 
    * @param string $path An absolute path to the directory to prepare
    * @param octal $permissions Permissions for newly created directories 
    * @return boolean|string
    * @return boolean:true The directory is ready for writing
    * @return string A message describing the failure
    */
   public static function prepare($path, $permissions = 0775)
   {
      if (file_exists($path)) {
         return Path::test($path, Path::IS_DIR | Path::IS_WRITABLE);
      }
      return @mkdir($path, $permissions, true)
         ? true
         : "failed to create directory '$path'";
   }

   /**
    * Remove wonky characters from a directory name
    *
    * @param string $name The basename to sanitize
    * @return string
    */
   public static function sanitizeName($name)
   {
      if (strpos($name, DIRECTORY_SEPARATOR) !== false) {
         throw new InvalidArgumentException(
            "invalid value provided for 'name'; ".
            "expecting a directory name that does not contain a path"
         );
      }

      return preg_replace('/[^A-Za-z0-9-_]/', '_', $name);
   }

   /**
    * Determine if a directory is empty
    * 
    * @param string $path An absolute path to test
    * @return boolean
    * @throws InvalidArgumentException If $path is not a string or doesn't exist
    */
   public static function isEmpty($path)
   {
      if (!is_string($path)) {
         throw new InvalidArgumentException(
            "invalid value provided for 'path'; ".
            "expecting a string"
         );
      }
      else if (($test = Path::test($path, Dir::READABLE)) !== true) {
         throw new InvalidArgumentException(
            "invalid value provided for 'path'; ".
            "expecting an absolute directory path; $test"
         );
      }
      return (count(scandir($path)) === 2);
   }

   /**
    * Recursively copy a directory
    *
    * @param string $source The absolute directory path to copy from
    * @param string $dest The absolute directory path to copy to
    * @param octal $mode Permissions for newly created directories
    * @return boolean|string
    * @return boolean:true The copy was successful
    * @return string An error message describing the failure
    */
   public static function copy($source, $dest, $mode = 0775)
   {
      if (($test = Dir::prepare($dest, $mode)) !== true) {
         return "failed to copy '$source' to '$dest'; $test";
      }
      else if (Dir::isEmpty($dest) === false) {
         return "failed to copy '$source'; '$dest' is not empty";
      }

      $dirIterator = new RDI($source, RDI::SKIP_DOTS);
      $iterator = new RII($dirIterator, RII::SELF_FIRST);
      foreach ($iterator as $file) {
         $destPath = $dest.DIRECTORY_SEPARATOR.$iterator->getSubPathName();
         if ($file->isDir()) {
            mkdir($destPath, $mode);
         }
         else {
            copy($file, $destPath);
         }
      }
      return true;
   }

   /**
    * Recursively remove a directory
    * 
    * @param string $dir An absolute path to a directory
    * @return boolean|string
    * @return boolean:true The directory was removed
    * @return string An error message describing the failure
    */
   public static function remove($dir)
   { 
      if (($test = Path::test($dir, Dir::READABLE_WRITABLE)) !== true) {
         return "failed to remove directory '$dir'; $test";
      }

      $files = array_diff(scandir($dir), ['.', '..']);
      foreach ($files as $file) {<commit_msg>updates Dir::copy to use Find::getIterator<commit_after><?php

namespace sndsgd\util;

use \InvalidArgumentException;


/**
 * Directory utility methods
 */
class Dir
{
   // bitmask values for use with sndsgd\Path::test()
   // @see sndsgd\Path for sub values
   const READABLE = 21;
   const WRITABLE = 13;
   const READABLE_WRITABLE = 29;

   /**
    * Verify if a dir is readable
    * 
    * @param string $path An absolute path to the dir to test
    * @return boolean|string
    * @return boolean:true The dir is readable
    * @return string A message describing the failure
    */
   public static function isReadable($path)
   {
      return Path::test($path, self::READABLE);
   }

   /**
    * Determine if a directory can be written to
    *
    * if the path does not exist, parent dirs will be analyzed
    * (the directory being tested does not have to exist)
    * @param string $path An absolute path to test
    * @return boolean|string
    * @return boolean:true The directory either exists, or can be created
    * @return string A message describing the failure
    */
   public static function isWritable($path)
   {
      while (file_exists($path) === false) {
         $path = dirname($path);
      }
      return Path::test($path, self::WRITABLE);
   }

   /**
    * Prepare a directory for writing
    * 
    * @param string $path An absolute path to the directory to prepare
    * @param octal $permissions Permissions for newly created directories 
    * @return boolean|string
    * @return boolean:true The directory is ready for writing
    * @return string A message describing the failure
    */
   public static function prepare($path, $permissions = 0775)
   {
      if (file_exists($path)) {
         return Path::test($path, Path::IS_DIR | Path::IS_WRITABLE);
      }
      return @mkdir($path, $permissions, true)
         ? true
         : "failed to create directory '$path'";
   }

   /**
    * Remove wonky characters from a directory name
    *
    * @param string $name The basename to sanitize
    * @return string
    */
   public static function sanitizeName($name)
   {
      if (strpos($name, DIRECTORY_SEPARATOR) !== false) {
         throw new InvalidArgumentException(
            "invalid value provided for 'name'; ".
            "expecting a directory name that does not contain a path"
         );
      }

      return preg_replace('/[^A-Za-z0-9-_]/', '_', $name);
   }

   /**
    * Determine if a directory is empty
    * 
    * @param string $path An absolute path to test
    * @return boolean
    * @throws InvalidArgumentException If $path is not a string or doesn't exist
    */
   public static function isEmpty($path)
   {
      if (!is_string($path)) {
         throw new InvalidArgumentException(
            "invalid value provided for 'path'; ".
            "expecting a string"
         );
      }
      else if (($test = Path::test($path, Dir::READABLE)) !== true) {
         throw new InvalidArgumentException(
            "invalid value provided for 'path'; ".
            "expecting an absolute directory path; $test"
         );
      }
      return (count(scandir($path)) === 2);
   }

   /**
    * Recursively copy a directory
    *
    * @param string $source The absolute directory path to copy from
    * @param string $dest The absolute directory path to copy to
    * @param octal $mode Permissions for newly created directories
    * @return boolean|string
    * @return boolean:true The copy was successful
    * @return string An error message describing the failure
    */
   public static function copy($source, $dest, $mode = 0775)
   {
      if (($test = Dir::prepare($dest, $mode)) !== true) {
         return "failed to copy '$source' to '$dest'; $test";
      }
      else if (Dir::isEmpty($dest) === false) {
         return "failed to copy '$source'; '$dest' is not empty";
      }

      $iterator = Find::getIterator($source, Find::RECURSIVE);
      foreach ($iterator as $file) {
         $destPath = $dest.DIRECTORY_SEPARATOR.$iterator->getSubPathName();
         if ($file->isDir()) {
            mkdir($destPath, $mode);
         }
         else {
            copy($file, $destPath);
         }
      }
      return true;
   }

   /**
    * Recursively remove a directory
    * 
    * @param string $dir An absolute path to a directory
    * @return boolean|string
    * @return boolean:true The directory was removed
    * @return string An error message describing the failure
    */
   public static function remove($dir)
   { 
      if (($test = Path::test($dir, Dir::READABLE_WRITABLE)) !== true) {
         return "failed to remove directory '$dir'; $test";
      }

      $files = array_diff(scandir($dir), ['.', '..']);
      foreach ($files as $file) {
{'fraction_non_alphanumeric': 0.11230002077706212, 'fraction_numerical': 0.0035321005609806775, 'mean_word_length': 2.4716913090515686, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4570665', 'n_tokens_mistral': 2752, 'n_tokens_neox': 2659, 'n_words': 939}