Ferreteria/v0.6/clade/Sys/FileSys/Mode/@current/2026/05/09

From WoozleCodes
Jump to navigation Jump to search
code as of 2026-05-09
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 {
    // SETUP
    static function FromCodes(string $s) : self;
    // ACCESS
    function IsValid() : bool;
    function NativeString() : ?string;
    function CanCreate()  : bool;
}
class cMode extends BaseClass implements SelfIface {

    // ++ CONFIG: reference data ++ //

    private const arModeMap = [
        'r'  => '+-- ', // reading only, from start of file
        'r+' => '++- ', // reading and writing; place the file pointer at the beginning of the file.
        'w'  => '-++0', // writing only; start of file; truncate to zero length. If the file does not exist, attempt to create it.
        'w+' => '+++0', // reading and writing; otherwise it has the same behavior as 'w'.
        'a'  => '-++>', // writing only; file pointer at end of file. Create if not found. fseek() has no effect, writes are always appended.
        'a+' => '+++>', // reading and writing; file pointer at end of file. Create if not found. fseek() only affects reading, writes appended.
        'x'  => '-++!', // writing only; file pointer at start of file. FAIL if found; create if not found.
        'x+' => '+++!', // reading and writing; otherwise it has the same behavior as 'x'.
        'c'  => '-++ ', // writing only. Create if not found. No action if found. File pointer at start of file.
        'c+' => '+++ ', // same as 'c' except reading and writing
      ];
    private const sModeChs = 'RWCF'; // This defines the order for the requirements in the ModeMap above.
    private const IDX_CREATE = 2; // "C" position

    // -- CONFIG -- //
    // ++ SETUP ++ //

    protected function __construct(){}

    public static function FromCodes(string $s) : SelfIface {
        $oThis = new static;
        $oThis->RequestString = $s;
        $oThis->ParseReqString($s);
        return $oThis;
    }
    public static function FromNative(string $s) : SelfIface {
        $oThis = new static;
        $oThis->NativeString($s);
        return $oThis;
    }

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

    private $sNative = NULL;
    public function NativeString(string $s=NULL) : ?string { return is_null($s) ? $this->sNative : ($this->sNative = $s); }
    public protected(set) string $RequestString;
    private $sFlags = NULL;
    public function FlagsString(string $s=NULL) : ?string { return is_null($s) ? $this->sFlags : ($this->sFlags = $s); }

    public function IsValid() : bool { return is_string($this->sFlags) || is_string($this->sNative); }

    // 2025-12-23 Can be made public if actually needed.
    protected function MustCreate()     : eTriState {
        return eTriState::tryFrom($this->FlagsString()[self::IDX_CREATE]);  // This result depends on the "CREATE" flag's value.
    }
    public function CanCreate() : bool { return $this->MustCreate() === eTriState::ON; }

    // ++ SETTINGS: processing ++ //

    protected function ParseReqString(string $sReq) {
        if (strlen($sReq) === 0) return;  // 2025-12-22 Should this throw an error?
        $sReq = strtoupper(str_replace(' ','',$sReq));  // remove spaces, lowercase

        $arModeOk = self::arModeMap; // allowable modes (to be winnowed down)
        $sRem = $sReq;
        while ($sRem !== '') {
            $chType = $sRem[0];
            if (strlen($sRem) === 1) {
                echo "Syntax error: letter-code [$chType] is missing an operator.".CRLF;
            } else {
                $chOper = $sRem[1];

                $sRem = substr($sRem,2);  // remove 1st 2 characters

                $eMode = eFileMode::tryFrom($chType);
                $eVal = $eMode->EnumForValue($chOper);
                #$this->AmHereShort("chType=[$chType] eMode: ".get_class($eMode).' eVal: '.get_class($eVal).' ('.$eVal->value.')');
                if (is_null($eVal)) {
                    echo $this->CodingPrompt("The operator '$chOper' (for '$chType' in [$sReq]) returned NULL.");
                } else {
                    // Filter available modes for this requirement:
                    $dxMode = strpos(self::sModeChs,$chType);
                    #$this->AmHereShort("mode-request to check: [$chType] -> index [$dxMode]");
                    foreach ($arModeOk as $sNatMode => $sReqs) {
                      #$this->AmHereShort("CHECKING $dxMode in [$sReqs] for [$sNatMode]");
                        if (isset($sReqs[$dxMode])) {
                            $chMapVal = $sReqs[$dxMode];  // check this mode's reqval
                            if ($chMapVal !== $chOper) {
                                // doesn't match requirement, so can't use it: remove from ok-list
                                unset($arModeOk[$sNatMode]);
                                #$this->AmHereShort("ELIMINATING $sNatMode");
                            }
                        }
                    }
                    #$this->AmHereShort('MODES REMAINING: '.count($arModeOk));
                }
            }
        }

        // If there are any entries left in $arModeOk, then we have a mode we can use.
        if (count($arModeOk) > 0) {
            // For now, we just use the first entry in what's left after winnowing.

            // Save the native mode-string:
            $arKeys = array_keys($arModeOk);
            $sNative = $arKeys[0];
            $this->NativeString($sNative);

            // Save the flags-string:
            $arVals = array_values($arModeOk);
            $sFlags = $arVals[0];
            $this->FlagsString($sFlags);
        } else {
            throw new \Exception("File mode-request '$sReq' did not match any valid modes.");
        }

        #$this->AmHereShort("NATIVE STRING: [".$this->NativeString().']');
    }

    // -- SETTINGS -- //
}