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 -- //
}