Ferreteria/v0.6/clade/Sys/FileSys/Mode

From Woozle Writes Code
< Ferreteria‎ | v0.6‎ | clade‎ | Sys‎ | FileSys
Jump to navigation Jump to search
clade: Sys\FileSys\Mode
Clade Family
StandardBase Mode (none)
Clade Aliases
Alias Clade
Subpages

About

  • Purpose: manages file-opening modes
    • ...because of course I have to be obsessive about it
  • Technical:

History

  • 2024-11-29 started
  • 2025-03-14 moved from [WFe]IO\fsys\Node\aux (part of two or three pre-existing file-handling clades) to [WFe]Sys/FileSys

Code

This includes several non-normalized entities (eFileMode, eTriState, eIfFound on the theory that they're only used internally, or at least are never referenced before any of the normalized clade-entities. I'm not entirely happy with this, but if it doesn't end up causing problems I'll probably leave it that way instead of breaking them up into their own files.

enum eFileMode : string {
    case READER  = 'R';  // want to read from file
    case WRITER  = 'W';  // want to write to file
    case CREATE  = 'C';  // create if not found
    case FOUND   = 'F';  // what to do if file found (exists)

    public function IsOptional() : bool { return $this->value == eTriState::SHRUG; }
    public function Default() : object { return ($this == self::FOUND) ? eIfFound::SHRUG : eTriState::SHRUG; }
    public function EnumForValue(string $ch) : eTriState | eIfFound {
        return ($this == self::FOUND) ? eIfFound::tryFrom($ch) : eTriState::tryFrom($ch);
    }
}
enum eTriState : string {
    case SHRUG = '';
    case OFF = '-';
    case ON = '+';

    public function IsRequired() : bool { return $this == self::ON; }
    public function IsForbidden() : bool { return $this == self::OFF; }
}
enum eIfFound : string {
    case SHRUG = ''; // not applicable / don't care
    case FAIL  = '!'; // fail/error
    case ZERO  = '0'; // reset file-length to zero (erase contents)
    case EOF   = '>'; // set file-pointer to EOF
}

/**
 * PURPOSE: compilation of all modes for use in assembling a request string, so we don't have to "use" so many enum classes.
 *  It also doesn't include the SHRUG values, since those don't have any effect (and would cause a duplicate enum value error).
 */
enum eMode : string {
    case FILE_READER  = eFileMode::READER->value;
    case FILE_WRITER  = eFileMode::WRITER->value;
    case FILE_CREATE  = eFileMode::CREATE->value;
    case FILE_FOUND   = eFileMode::FOUND->value;

    case FOUND_FAIL  = eIfFound::FAIL->value; // fail/error
    case FOUND_ZERO  = eIfFound::ZERO->value; // reset file-length to zero (erase contents)
    case FOUND_EOF   = eIfFound::EOF->value; // set file-pointer to EOF

    case FLAG_OFF = eTriState::OFF->value;
    case FLAG_ON = eTriState::ON->value;
}
interface iMode extends BaseIface {
    // SETTINGS
    function Request(string $sReq=NULL) : string;
    function IsValid() : bool;
    // ACCESS
    function NativeString() : ?string;
    function IsReader(eTriState $e=NULL)   : eTriState;
    function IsWriter(eTriState $e=NULL)   : eTriState;
    function CanCreate(eTriState $e=NULL)  : eTriState;
    function DoIfFound(eIfFound $do=NULL)  : eIfFound;
}
class cMode extends BaseClass implements iMode {

    // ++ SETUP ++ //

    /**
     * INPUT:
     *  if string: enum codes defined above
     *  if array: array of enums defined above
     */
    public function __construct(string|array $vRequest) {
        if (is_array($vRequest)) {
            $sRequest = '';
            foreach ($vRequest as $eReq) {
                $sRequest .= $eReq->value;
            }
        } else {
            $sRequest = $vRequest;
        }
        $this->Request($sRequest);
    }

    // -- SETUP -- //
    // ++ SETTINGS ++ //

    private $sReq;
    public function Request(string $sReq=NULL) : string {
        if (is_string($sReq)) {
            $this->sReq = $sReq;
            $this->ParseRequest();
        } else {
            $sReq = $this->sReq;
        }
        return $sReq;
    }
    // TODO: cache NativeString(), maybe?
    public function IsValid() : bool { return $this->Request() != '' && is_string($this->NativeString()); }

    protected function ParseRequest() {
        $sReq = $this->Request();

        if (strlen($sReq) == 0) return;
        $sReq = strtoupper(str_replace(' ','',$sReq));  // remove spaces, lowercase

        $nIdx = 0;
        $nLen = strlen($sReq);
        do {
            $chReq = $sReq[$nIdx++];
            $chVal = $sReq[$nIdx++];
            $eMode = eFileMode::tryFrom($chReq);
            $eVal = $eMode->EnumForValue($chVal);
            if (is_null($eVal)) {
                $sReq = $this->Request();
                echo $this->CodingPrompt("The value '$chVal' (for '$chReq' in [$sReq]) returned NULL.");
            } else {
                $arData[$chReq] = $eVal;
            }

        } while ($nIdx < $nLen);

        #echo 'SETTING arData TO:'.CRLF.print_r($arData,TRUE);

        $this->arData = $arData;
    }

    // ++ VALUES ++ //

    public function NativeString() : ?string {
        #self::GotToHere('calculating native string');
        $sMode = NULL;

        $ecRd = $this->IsReader();
        $ecWr = $this->IsWriter();
        $ecCr = $this->CanCreate();
        $ecEx = $this->DoIfFound();

        switch ($ecRd) {
          case eTriState::SHRUG:  // read OPTIONAL
            $sMode = $this->NativeString_forReadShrug();
            break;
          case eTriState::OFF:  // read OFF
            $sMode = $this->NativeString_forReadForbid();
            break;
          case eTriState::ON:   // read ON
            $sMode = $this->NativeString_forReadDemand();
            break;
        }
        #$sRd = $ecRd->name;
        #$sWr = $ecWr->name;
        #$sCr = $ecCr->name;
        #$sEx = $ecEx->name;
        #self::GotToHere("sMode=[$sMode] Rd=[$sRd] Wr=[$sWr] Cr=[$sCr] Ex=[$sEx]");

        return $sMode;
    }
    // CONDITION: readability optional/ok
    protected function NativeString_forReadShrug() : string {
        $ecWr = $this->IsWriter();
        #self::GotToHere('READ: optional');
        // OPTIONAL readability
        switch ($ecWr) {
          case eTriState::OFF:  // read OPTIONAL, write OFF
            echo $this->CodingPrompt("Must request R and/or W mode.");
            break;
          case eTriState::ON: // read OPTIONAL, write ON
            #self::GotToHere('WRITE: on');
            $ecCr = $this->CanCreate();
            switch ($ecCr) {
              case eTriState::OFF:    // read OPTIONAL, write ON, NO CREATE
              case eTriState::SHRUG:  // we'll let SHRUG default to NO CREATE
                $sMode = 'r+';  // read-write, BOF
                break;
              case eTriState::ON:  // read OPTIONAL, write ON, CREATE if n/f
                // We're writing, creating new file, zeroing existing file -- can assume reading not needed
                $sMode = 'w+';  // (if we want to assume reading, use r+
                break;
              default:
                self::GotToHere('CREATE switch is broken, somehow.');
            }
            break;
              case eTriState::SHRUG: // read OPTIONAL, write OPTIONAL
                $this->RequestPrompt("Both read and write are optional - what are we actually trying to do?");
                break;
              default:
                self::GotToHere('WRITE switch is broken, somehow.');
        }
        return $sMode;
    }
    // CONDITION: must NOT be readable
    protected function NativeString_forReadForbid() : string {
        $ecWr = $this->IsWriter();
        #self::GotToHere('READ: no');
        // NOT readable
        switch ($ecWr) {
          case eTriState::OFF:  // read OFF, write OFF
            echo $this->CodingPrompt("Must request R and/or W mode.");
            break;
          case eTriState::ON: // read OFF, write ON
            // WRITE-ONLY
            self::HardAssert($ecCr->value->IsOptional(),"Can't force do-not-create on a write-only file.");
            switch($ecEx) {
              case eIfFound::SHRUG: $sMode = 'c'; break;
              case eIfFound::EOF:   $sMode = 'a'; break;
              case eIfFound::FAIL:  $sMode = 'x'; break;
              case eIfFound::ZERO:  $sMode = 'w'; break;
              default: self::GotToHere("IfFound switch [$ecEx] is broken somehow");
            }
            break;
        }
    }
    // CONDITION: must be readable
    protected function NativeString_forReadDemand() : string {
        $ecWr = $this->IsWriter();
        #self::GotToHere('READ: yes');
        switch ($ecWr) {
          case eTriState::OFF:  // read ON, write OFF
            // READ-ONLY
            // There's only one read-only mode:
            $sMode = 'r';
            self::SoftAssert($ecCr->value != eTriState::ON,"Can't force CREATE on a read-only file.");
            break;

          case eTriState::ON:
            // READ+WRITE
            $ecEx = $this->DoIfFound();
            switch($ecEx) {
              case eIfFound::SHRUG: $sMode = 'c+'; break;
              case eIfFound::EOF:   $sMode = 'a+'; break;
              case eIfFound::FAIL:  $sMode = 'x+'; break;
              case eIfFound::ZERO:  $sMode = 'w+'; break;
              default: self::GotToHere("IfFound switch [$ecEx] is broken somehow");
            }
            break;

          case eTriState::SHRUG:  $sMode = 'r'; break;  // grant as little permission as needed
        }
        return $sMode;
    }

    protected function RequestPrompt(string $sDetails) {
        $sReq = $this->Request();
        echo $this->CodingPrompt("'$sReq' is a rather ambiguous request. $sDetails");
    }

    private $arData = [];
    protected function Modes(eFileMode $eMode, $eVal=NULL) {
        $sv = $eMode->value;  // index for array lookup
        #echo "sv=[$sv] eVal=".(is_null($eVal) ? '(NULL)' : $eVal->value).CRLF;
        if (is_null($eVal)) {
            $ar = $this->arData;
            if (array_key_exists($sv,$ar)) {
                $eVal = $ar[$sv];
                #echo "sv=[$sv] FOUND eVal=".(is_null($eVal) ? '(NULL)' : $eVal->value).CRLF;
            } else {
                $eVal = $eMode->Default();
                #echo "sv=[$sv] DEFAULT eVal=".(is_null($eVal) ? '(NULL)' : get_class($eVal)).CRLF;
            }
        } else {
            $this->arData[$sv] = $eVal;
            #echo "sv=[$sv] SET eVal=".(is_null($eVal) ? '(NULL)' : $eVal->value).CRLF;
        }
        return $eVal;
    }

    // These are PUBLIC in case we need to do other things (e.g. create parent-folder) depending on mode:
    public function IsReader(eTriState $e=NULL)   : eTriState { return $this->Modes(eFileMode::READER,$e); }
    public function IsWriter(eTriState $e=NULL)   : eTriState { return $this->Modes(eFileMode::WRITER,$e); }
    public function CanCreate(eTriState $e=NULL)  : eTriState { return $this->Modes(eFileMode::CREATE,$e); }
    public function DoIfFound(eIfFound $do=NULL)  : eIfFound  { return $this->Modes(eFileMode::FOUND,$do); }

    // -- VALUES -- //
}