Browse Source

Change `IsValidConfigName()` into `IsValidname()`

There are many occasions for Acedia where we might want to limit names
of objects to a certain predictable and manageable set and limitations
placed on config names seem to fit all of these cases.
That is why we rename `IsValidConfigName()` into a more
generic-sounding method name.

Additional change is limiting length of a "valid name" to 50 characters
at most, since Unreal Engine doesn't like loing config lines.
pull/8/head
Anton Tarasenko 2 years ago
parent
commit
50b8bb30a1
  1. 52
      sources/Config/AcediaConfig.uc
  2. 26
      sources/Text/BaseText.uc

52
sources/Config/AcediaConfig.uc

@ -5,8 +5,8 @@
* Making a child class for `AcediaConfig` defines "<data_class_name>" and * Making a child class for `AcediaConfig` defines "<data_class_name>" and
* contents of appropriate section. Then any such class can have multiple * contents of appropriate section. Then any such class can have multiple
* records with different "<config_object_name>" values. The only requirement * records with different "<config_object_name>" values. The only requirement
* is that "<config_object_name>" must only contain latin letters, digits, * is that "<config_object_name>" must be considered *valid* by
* dot ('.') and underscore ('_'). Otherwise they will be ignored. * `BaseText.IsValidName()` standards, otherwise method will return `none`.
* *
* Copyright 2021 Anton Tarasenko * Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
@ -44,6 +44,14 @@ class AcediaConfig extends AcediaObject
* it is automatically converted into colon ':' character to allow its * it is automatically converted into colon ':' character to allow its
* storage inside ini config files. It also will not lead to the name * storage inside ini config files. It also will not lead to the name
* conflicts, since colon is a forbidden character for `AcediaConfig`. * conflicts, since colon is a forbidden character for `AcediaConfig`.
* Unreal Engine also doesn't handle long object names very well:
* if it considers "[<config_object_name> <data_class_name>]" too long, it
* might cut some of the trailing letters of what is between brackets '['
* and ']' basically mangling class' name. Since such config header seems
* to be able to contain at least 100 character (at least in our tests),
* we deal with that issue by limiting name to 50 characters at most.
* Depending on the class' name it might still cause problems, so don't
* make them too long.
* 2. Behavior of loading `perobjectconfig`-objects a second time is wonky and * 2. Behavior of loading `perobjectconfig`-objects a second time is wonky and
* is fixed by `AcediaConfig`: it provides concrete behavior guarantees * is fixed by `AcediaConfig`: it provides concrete behavior guarantees
* for all of its config object-managing methods. * for all of its config object-managing methods.
@ -119,7 +127,7 @@ public static function Initialize()
continue; continue;
} }
nextName = __().text.FromString(NameToActualVersion(names[i])); nextName = __().text.FromString(NameToActualVersion(names[i]));
if (nextName.IsValidConfigName()) { if (nextName.IsValidName()) {
default.existingConfigs.SetItem(nextName.LowerCopy(), none); default.existingConfigs.SetItem(nextName.LowerCopy(), none);
} }
nextName.FreeSelf(); nextName.FreeSelf();
@ -140,14 +148,14 @@ private static function string NameToActualVersion(string configObjectName)
* Creates a brand new config object with a given name. * Creates a brand new config object with a given name.
* *
* Fails if config object with that name already exists. * Fails if config object with that name already exists.
* Names are case-insensitive, must contain only ASCII latin letters, digits * Config name must be considered *valid* by `BaseText.IsValidName()`
* and dot ('.') character. * standards, otherwise method will return `none`.
* *
* Always writes new config inside the ini file on disk. * Always writes new config inside the ini file on disk.
* *
* @param name Name of the new config object. * @param name Name of the new config object.
* Case-insensitive, must contain only ASCII latin letters, digits * Must be considered *valid* by `BaseText.IsValidName()`
* and dot ('.') character. * standards, otherwise method will fail.
* @return `false` iff config object name `name` already existed * @return `false` iff config object name `name` already existed
* or `name` is invalid for config object. * or `name` is invalid for config object.
*/ */
@ -155,7 +163,7 @@ public final static function bool NewConfig(BaseText name)
{ {
local AcediaConfig newConfig; local AcediaConfig newConfig;
if (name == none) return false; if (name == none) return false;
if (!name.IsValidConfigName()) return false; if (!name.IsValidName()) return false;
if (default.existingConfigs == none) return false; if (default.existingConfigs == none) return false;
name = name.LowerCopy(); name = name.LowerCopy();
@ -176,19 +184,16 @@ public final static function bool NewConfig(BaseText name)
/** /**
* Checks if a config object with a given name exists. * Checks if a config object with a given name exists.
* *
* Names are case-insensitive, must contain only ASCII latin letters, digits
* and dot ('.') character.
*
* @param name Name of the new config object. * @param name Name of the new config object.
* Case-insensitive, must contain only ASCII latin letters, digits * Must be considered *valid* by `BaseText.IsValidName()` standards,
* and dot ('.') character. * otherwise method will return `false`.
* @return `true` iff new config object was created. * @return `true` iff new config object was created.
*/ */
public final static function bool Exists(BaseText name) public final static function bool Exists(BaseText name)
{ {
local bool result; local bool result;
if (name == none) return false; if (name == none) return false;
if (!name.IsValidConfigName()) return false; if (!name.IsValidName()) return false;
if (default.existingConfigs == none) return false; if (default.existingConfigs == none) return false;
name = name.LowerCopy(); name = name.LowerCopy();
@ -200,17 +205,12 @@ public final static function bool Exists(BaseText name)
/** /**
* Deletes config object with a given name. * Deletes config object with a given name.
* *
* Names are case-insensitive, must contain only ASCII latin letters, digits
* and dot ('.') character.
*
* If given config object exists, this method cannot fail. * If given config object exists, this method cannot fail.
* `Exists()` is guaranteed to return `false` after this method call. * `Exists()` is guaranteed to return `false` after this method call.
* *
* Always removes any present config entries from ini files. * Always removes any present config entries from ini files.
* *
* @param name Name of the config object to delete. * @param name Name of the config object to delete.
* Case-insensitive, must contain only ASCII latin letters, digits
* and dot ('.') character.
*/ */
public final static function DeleteConfig(BaseText name) public final static function DeleteConfig(BaseText name)
{ {
@ -244,15 +244,15 @@ public static function array<Text> AvailableConfigs()
* Returns `AcediaConfig` of caller class with name `name`. * Returns `AcediaConfig` of caller class with name `name`.
* *
* @param name Name of the config object, whos settings data is to * @param name Name of the config object, whos settings data is to
* be loaded. Case-insensitive, must contain only ASCII latin letters, * be loaded. Must be considered *valid* by `BaseText.IsValidName()`
* digits and dot ('.') character. * standards, otherwise method will return `none`.
* @return `AcediaConfig` of caller class with name `name`. * @return `AcediaConfig` of caller class with name `name`.
*/ */
public final static function AcediaConfig GetConfigInstance(BaseText name) public final static function AcediaConfig GetConfigInstance(BaseText name)
{ {
local AssociativeArray.Entry configEntry; local AssociativeArray.Entry configEntry;
if (name == none) return none; if (name == none) return none;
if (!name.IsValidConfigName()) return none; if (!name.IsValidName()) return none;
if (default.existingConfigs == none) return none; if (default.existingConfigs == none) return none;
name = name.LowerCopy(); name = name.LowerCopy();
@ -276,8 +276,8 @@ public final static function AcediaConfig GetConfigInstance(BaseText name)
* `true`. * `true`.
* *
* @param name Name of the config object, whos data is to be loaded. * @param name Name of the config object, whos data is to be loaded.
* Case-insensitive, must contain only ASCII latin letters, digits * Name must be considered *valid* by `BaseText.IsValidName()` standards,
* and dot ('.') character. * otherwise method will return `false`.
* @return Data of a particular config object, given by the `name`. * @return Data of a particular config object, given by the `name`.
* Expected to be in format that allows for JSON serialization * Expected to be in format that allows for JSON serialization
* (see `JSONAPI.IsCompatible()` for details). * (see `JSONAPI.IsCompatible()` for details).
@ -303,8 +303,8 @@ public final static function AssociativeArray LoadData(BaseText name)
* `true`. * `true`.
* *
* @param name Name of the config object, whos data is to be modified. * @param name Name of the config object, whos data is to be modified.
* Case-insensitive, must contain only ASCII latin letters, digits * Name must be considered *valid* by `BaseText.IsValidName()` standards,
* and dot ('.') character. * otherwise method will return `false`.
* @param data New data for config variables. Expected to be in format that * @param data New data for config variables. Expected to be in format that
* allows for JSON deserialization (see `JSONAPI.IsCompatible()` for * allows for JSON deserialization (see `JSONAPI.IsCompatible()` for
* details). * details).

26
sources/Text/BaseText.uc

@ -405,25 +405,37 @@ public final function MutableText UpperMutableCopy(
} }
/** /**
* Checks if caller `BaseText` contains a valid name for a config object or * Checks if caller `BaseText` contains a valid name object or not.
* not.
* *
* Valid names contain only ASCII characters, digits and '.' / '_' characters. * Valid names are case-insensitive `BaseText`s that:
* Empty `BaseText` is not considered a valid name. * 1. No longer than 50 characters long;
* 2. Contain only ASCII characters, digits and '.' / '_' characters;
* 3. Has to start with a latin letter or '_';
* 4. Empty `BaseText` is not considered a valid name.
* *
* @return `true` if caller `BaseText` contains a valid config object name and * @return `true` if caller `BaseText` contains a valid config object name and
* `false` otherwise. * `false` otherwise.
*/ */
public final function bool IsValidConfigName() public final function bool IsValidName()
{ {
local int i; local int i;
local int codePoint; local int codePoint;
local bool isValidCodePoint; local bool isValidCodePoint;
local Character nextCharacter; local Character nextCharacter;
if (IsEmpty()) {
if (IsEmpty()) return false;
if (GetLength() > 50) return false;
// Test first character separately, since it cannot be digit
nextCharacter = GetCharacter(0);
codePoint = nextCharacter.codePoint;
isValidCodePoint = ( codePoint == 0x5F // '_'
|| (0x41 <= codePoint && codePoint <= 0x5A) // 'A' to 'Z'
|| (0x61 <= codePoint && codePoint <= 0x7A)); // 'a' to 'z'
if (!isValidCodePoint) {
return false; return false;
} }
for (i = 0; i < GetLength(); i += 1) for (i = 1; i < GetLength(); i += 1)
{ {
nextCharacter = GetCharacter(i); nextCharacter = GetCharacter(i);
codePoint = nextCharacter.codePoint; codePoint = nextCharacter.codePoint;

Loading…
Cancel
Save