Add has_extension() method to String

This commit is contained in:
kobewi 2025-08-08 17:56:19 +02:00
parent ca452113d4
commit a33ae0be0e
29 changed files with 46 additions and 48 deletions

View file

@ -231,8 +231,7 @@ Error ResourceFormatSaverCrypto::save(const Ref<Resource> &p_resource, const Str
if (cert.is_valid()) {
err = cert->save(p_path);
} else if (key.is_valid()) {
String el = p_path.get_extension().to_lower();
err = key->save(p_path, el == "pub");
err = key->save(p_path, p_path.has_extension("pub"));
} else {
ERR_FAIL_V(ERR_INVALID_PARAMETER);
}

View file

@ -877,8 +877,7 @@ bool GDExtensionResourceLoader::handles_type(const String &p_type) const {
}
String GDExtensionResourceLoader::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
if (el == "gdextension") {
if (p_path.has_extension("gdextension")) {
return "GDExtension";
}
return "";

View file

@ -1565,8 +1565,7 @@ bool ResourceFormatLoaderJSON::handles_type(const String &p_type) const {
}
String ResourceFormatLoaderJSON::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
if (el == "json") {
if (p_path.has_extension("json")) {
return "JSON";
}
return "";

View file

@ -105,7 +105,6 @@ Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path,
}
ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Can't save resource to empty path. Provide non-empty path or a Resource with non-empty resource_path.");
String extension = path.get_extension();
Error err = ERR_FILE_UNRECOGNIZED;
for (int i = 0; i < saver_count; i++) {

View file

@ -367,7 +367,7 @@ bool TranslationLoaderPO::handles_type(const String &p_type) const {
}
String TranslationLoaderPO::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "po" || p_path.get_extension().to_lower() == "mo") {
if (p_path.has_extension("po") || p_path.has_extension("mo")) {
return "Translation";
}
return "";

View file

@ -520,6 +520,8 @@ public:
String get_basename() const;
String path_join(const String &p_path) const;
char32_t unicode_at(int p_idx) const;
bool has_extension(const char *p_ext) const { return get_extension().to_lower() == p_ext; }
bool has_extension(const String &p_ext) const { return get_extension().to_lower() == p_ext; }
CharString ascii(bool p_allow_extended = false) const;
// Parse an ascii string.

View file

@ -1511,7 +1511,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
// Just store it as it comes.
// Customization only happens if plugins did not take care of it before.
bool force_binary = convert_text_to_binary && (path.get_extension().to_lower() == "tres" || path.get_extension().to_lower() == "tscn");
bool force_binary = convert_text_to_binary && (path.has_extension("tres") || path.has_extension("tscn"));
String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, force_binary);
if (export_path != path) {

View file

@ -2511,7 +2511,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import
}
script_ext_hint += "*." + E;
}
bool trimming_defaults_on = p_path.get_extension().to_lower() == "fbx";
bool trimming_defaults_on = p_path.has_extension("fbx");
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "nodes/apply_root_scale"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "nodes/root_scale", PROPERTY_HINT_RANGE, "0.001,1000,0.001"), 1.0));

View file

@ -140,7 +140,7 @@ void ProjectManager::_notification(int p_what) {
// Utility data.
Ref<Texture2D> ProjectManager::_file_dialog_get_icon(const String &p_path) {
if (p_path.get_extension().to_lower() == "godot") {
if (p_path.has_extension("godot")) {
return singleton->icon_type_cache["GodotMonochrome"];
}
@ -148,7 +148,7 @@ Ref<Texture2D> ProjectManager::_file_dialog_get_icon(const String &p_path) {
}
Ref<Texture2D> ProjectManager::_file_dialog_get_thumbnail(const String &p_path) {
if (p_path.get_extension().to_lower() == "godot") {
if (p_path.has_extension("godot")) {
return singleton->icon_type_cache["GodotFile"];
}

View file

@ -42,7 +42,6 @@ void GPUParticlesCollisionSDF3DEditorPlugin::_bake() {
if (path.is_empty()) {
path = "res://" + col_sdf->get_name() + "_data.exr";
} else {
String ext = path.get_extension();
path = path.get_basename() + "." + col_sdf->get_name() + "_data.exr";
}
probe_file->set_current_path(path);

View file

@ -44,7 +44,6 @@ void VoxelGIEditorPlugin::_bake() {
if (path.is_empty()) {
path = "res://" + voxel_gi->get_name() + "_data.res";
} else {
String ext = path.get_extension();
path = path.get_basename() + "." + voxel_gi->get_name() + "_data.res";
}
probe_file->set_current_path(path);

View file

@ -708,7 +708,7 @@ bool ResourceFormatDDS::handles_type(const String &p_type) const {
}
String ResourceFormatDDS::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "dds") {
if (p_path.has_extension("dds")) {
return "Texture";
}
return "";

View file

@ -94,7 +94,7 @@ Variant EditorSceneFormatImporterUFBX::get_option_visibility(const String &p_pat
void EditorSceneFormatImporterUFBX::get_import_options(const String &p_path,
List<ResourceImporter::ImportOption> *r_options) {
// Returns all the options when path is empty because that means it's for the Project Settings.
if (p_path.is_empty() || p_path.get_extension().to_lower() == "fbx") {
if (p_path.is_empty() || p_path.has_extension("fbx")) {
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/importer", PROPERTY_HINT_ENUM, "ufbx,FBX2glTF"), FBX_IMPORTER_UFBX));
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::BOOL, "fbx/allow_geometry_helper_nodes"), false));
r_options->push_back(ResourceImporterScene::ImportOption(PropertyInfo(Variant::INT, "fbx/embedded_image_handling", PROPERTY_HINT_ENUM, "Discard All Textures,Extract Textures,Embed as Basis Universal,Embed as Uncompressed", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), FBXState::HANDLE_BINARY_EXTRACT_TEXTURES));

View file

@ -492,7 +492,7 @@ Error GDScriptAnalyzer::resolve_class_inheritance(GDScriptParser::ClassNode *p_c
}
} else if (ProjectSettings::get_singleton()->has_autoload(name) && ProjectSettings::get_singleton()->get_autoload(name).is_singleton) {
const ProjectSettings::AutoloadInfo &info = ProjectSettings::get_singleton()->get_autoload(name);
if (info.path.get_extension().to_lower() != GDScriptLanguage::get_singleton()->get_extension()) {
if (!info.path.has_extension(GDScriptLanguage::get_singleton()->get_extension())) {
push_error(vformat(R"(Singleton %s is not a GDScript.)", info.name), id);
return ERR_PARSE_ERROR;
}

View file

@ -76,7 +76,7 @@ Error GDScriptParserRef::raise_status(Status p_new_status) {
get_parser()->clear();
status = PARSED;
String remapped_path = ResourceLoader::path_remap(path);
if (remapped_path.get_extension().to_lower() == "gdc") {
if (remapped_path.has_extension("gdc")) {
Vector<uint8_t> tokens = GDScriptCache::get_binary_tokens(remapped_path);
source_hash = hash_djb2_buffer(tokens.ptr(), tokens.size());
result = get_parser()->parse_binary(tokens, path);
@ -313,7 +313,7 @@ Ref<GDScript> GDScriptCache::get_shallow_script(const String &p_path, Error &r_e
Ref<GDScript> script;
script.instantiate();
script->set_path(p_path, true);
if (remapped_path.get_extension().to_lower() == "gdc") {
if (remapped_path.has_extension("gdc")) {
Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
r_error = ERR_FILE_CANT_READ;
@ -364,7 +364,7 @@ Ref<GDScript> GDScriptCache::get_full_script(const String &p_path, Error &r_erro
const String remapped_path = ResourceLoader::path_remap(p_path);
if (p_update_from_disk) {
if (remapped_path.get_extension().to_lower() == "gdc") {
if (remapped_path.has_extension("gdc")) {
Vector<uint8_t> buffer = get_binary_tokens(remapped_path);
if (buffer.is_empty()) {
r_error = ERR_FILE_CANT_READ;

View file

@ -1120,7 +1120,7 @@ static void _list_available_types(bool p_inherit_only, GDScriptParser::Completio
for (const KeyValue<StringName, ProjectSettings::AutoloadInfo> &E : autoloads) {
const ProjectSettings::AutoloadInfo &info = E.value;
if (!info.is_singleton || info.path.get_extension().to_lower() != "gd") {
if (!info.is_singleton || !info.path.has_extension("gd")) {
continue;
}
ScriptLanguage::CodeCompletionOption option(info.name, ScriptLanguage::CODE_COMPLETION_KIND_CLASS, ScriptLanguage::LOCATION_OTHER_USER_CODE);

View file

@ -282,7 +282,7 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
} else if (binary_tokens && next.ends_with(".textonly.gd")) {
next = dir->get_next();
continue;
} else if (next.get_extension().to_lower() == "gd") {
} else if (next.has_extension("gd")) {
#ifndef DEBUG_ENABLED
// On release builds, skip tests marked as debug only.
Error open_err = OK;

View file

@ -335,7 +335,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, const String &p_scene_import_type, const String &p_option,
const HashMap<StringName, Variant> &p_options) {
if (p_path.get_extension().to_lower() != "blend") {
if (!p_path.has_extension("blend")) {
return true;
}
@ -349,7 +349,7 @@ Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_pa
void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, List<ResourceImporter::ImportOption> *r_options) {
// Returns all the options when path is empty because that means it's for the Project Settings.
if (!p_path.is_empty() && p_path.get_extension().to_lower() != "blend") {
if (!p_path.is_empty() && !p_path.has_extension("blend")) {
return;
}
#define ADD_OPTION_BOOL(PATH, VALUE) \

View file

@ -39,7 +39,7 @@ AudioServer::SpeakerMode MovieWriterMJPEG::get_audio_speaker_mode() const {
}
bool MovieWriterMJPEG::handles_file(const String &p_path) const {
return p_path.get_extension().to_lower() == "avi";
return p_path.has_extension("avi");
}
void MovieWriterMJPEG::get_supported_extensions(List<String> *r_extensions) const {

View file

@ -553,7 +553,7 @@ bool ResourceFormatKTX::handles_type(const String &p_type) const {
}
String ResourceFormatKTX::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "ktx" || p_path.get_extension().to_lower() == "ktx2") {
if (p_path.has_extension("ktx") || p_path.has_extension("ktx2")) {
return "ImageTexture";
}
return "";

View file

@ -2892,7 +2892,7 @@ bool ResourceFormatLoaderCSharpScript::handles_type(const String &p_type) const
}
String ResourceFormatLoaderCSharpScript::get_resource_type(const String &p_path) const {
return p_path.get_extension().to_lower() == "cs" ? CSharpLanguage::get_singleton()->get_type() : "";
return p_path.has_extension("cs") ? CSharpLanguage::get_singleton()->get_type() : "";
}
Error ResourceFormatSaverCSharpScript::save(const Ref<Resource> &p_resource, const String &p_path, uint32_t p_flags) {

View file

@ -100,7 +100,7 @@ AudioServer::SpeakerMode MovieWriterOGV::get_audio_speaker_mode() const {
}
bool MovieWriterOGV::handles_file(const String &p_path) const {
return p_path.get_extension().to_lower() == "ogv";
return p_path.has_extension("ogv");
}
void MovieWriterOGV::get_supported_extensions(List<String> *r_extensions) const {

View file

@ -819,8 +819,7 @@ bool ResourceFormatLoaderTheora::handles_type(const String &p_type) const {
}
String ResourceFormatLoaderTheora::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
if (el == "ogv") {
if (p_path.has_extension("ogv")) {
return "VideoStreamTheora";
}
return "";

View file

@ -485,7 +485,7 @@ bool ResourceFormatLoaderCompressedTexture2D::handles_type(const String &p_type)
}
String ResourceFormatLoaderCompressedTexture2D::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "ctex") {
if (p_path.has_extension("ctex")) {
return "CompressedTexture2D";
}
return "";
@ -669,7 +669,7 @@ bool ResourceFormatLoaderCompressedTexture3D::handles_type(const String &p_type)
}
String ResourceFormatLoaderCompressedTexture3D::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "ctex3d") {
if (p_path.has_extension("ctex3d")) {
return "CompressedTexture3D";
}
return "";
@ -843,15 +843,15 @@ CompressedTextureLayered::~CompressedTextureLayered() {
Ref<Resource> ResourceFormatLoaderCompressedTextureLayered::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) {
Ref<CompressedTextureLayered> ct;
if (p_path.get_extension().to_lower() == "ctexarray") {
if (p_path.has_extension("ctexarray")) {
Ref<CompressedTexture2DArray> c;
c.instantiate();
ct = c;
} else if (p_path.get_extension().to_lower() == "ccube") {
} else if (p_path.has_extension("ccube")) {
Ref<CompressedCubemap> c;
c.instantiate();
ct = c;
} else if (p_path.get_extension().to_lower() == "ccubearray") {
} else if (p_path.has_extension("ccubearray")) {
Ref<CompressedCubemapArray> c;
c.instantiate();
ct = c;
@ -883,13 +883,13 @@ bool ResourceFormatLoaderCompressedTextureLayered::handles_type(const String &p_
}
String ResourceFormatLoaderCompressedTextureLayered::get_resource_type(const String &p_path) const {
if (p_path.get_extension().to_lower() == "ctexarray") {
if (p_path.has_extension("ctexarray")) {
return "CompressedTexture2DArray";
}
if (p_path.get_extension().to_lower() == "ccube") {
if (p_path.has_extension("ccube")) {
return "CompressedCubemap";
}
if (p_path.get_extension().to_lower() == "ccubearray") {
if (p_path.has_extension("ccubearray")) {
return "CompressedCubemapArray";
}
return "";

View file

@ -1466,7 +1466,7 @@ void ResourceFormatLoaderText::get_classes_used(const String &p_path, HashSet<St
}
String ResourceFormatLoaderText::get_resource_type(const String &p_path) const {
String ext = p_path.get_extension().to_lower();
const String ext = p_path.get_extension().to_lower();
if (ext == "tscn") {
return "PackedScene";
} else if (ext != "tres") {
@ -1488,8 +1488,7 @@ String ResourceFormatLoaderText::get_resource_type(const String &p_path) const {
}
String ResourceFormatLoaderText::get_resource_script_class(const String &p_path) const {
String ext = p_path.get_extension().to_lower();
if (ext != "tres") {
if (!p_path.has_extension("tres")) {
return String();
}
@ -1507,8 +1506,7 @@ String ResourceFormatLoaderText::get_resource_script_class(const String &p_path)
}
ResourceUID::ID ResourceFormatLoaderText::get_resource_uid(const String &p_path) const {
String ext = p_path.get_extension().to_lower();
const String ext = p_path.get_extension().to_lower();
if (ext != "tscn" && ext != "tres") {
return ResourceUID::INVALID_ID;
}

View file

@ -338,8 +338,7 @@ bool ResourceFormatLoaderShader::handles_type(const String &p_type) const {
}
String ResourceFormatLoaderShader::get_resource_type(const String &p_path) const {
String el = p_path.get_extension().to_lower();
if (el == "gdshader") {
if (p_path.has_extension("gdshader")) {
return "Shader";
}
return "";

View file

@ -120,8 +120,7 @@ bool ResourceFormatLoaderShaderInclude::handles_type(const String &p_type) const
}
String ResourceFormatLoaderShaderInclude::get_resource_type(const String &p_path) const {
String extension = p_path.get_extension().to_lower();
if (extension == "gdshaderinc") {
if (p_path.has_extension("gdshaderinc")) {
return "ShaderInclude";
}
return "";

View file

@ -44,7 +44,7 @@ void MovieWriterPNGWAV::get_supported_extensions(List<String> *r_extensions) con
}
bool MovieWriterPNGWAV::handles_file(const String &p_path) const {
return p_path.get_extension().to_lower() == "png";
return p_path.has_extension("png");
}
String MovieWriterPNGWAV::zeros_str(uint32_t p_index) {

View file

@ -1797,6 +1797,13 @@ TEST_CASE("[String] Path functions") {
CHECK(String(path[i]).simplify_path().get_base_dir().path_join(file[i]) == String(path[i]).simplify_path());
}
CHECK(String("res://test.png").has_extension("png"));
CHECK(String("res://test.PNG").has_extension("png"));
CHECK_FALSE(String("res://test.png").has_extension("jpg"));
CHECK_FALSE(String("res://test.png/README").has_extension("png"));
CHECK_FALSE(String("res://test.").has_extension("png"));
CHECK_FALSE(String("res://test").has_extension("png"));
static const char *file_name[3] = { "test.tscn", "test://.xscn", "?tes*t.scn" };
static const bool valid[3] = { true, false, false };
for (int i = 0; i < 3; i++) {