class EL_PATH_BASE_NAME

(source code)

description

Modify or query the base name (last step) of path conforming to EL_PATH

note
	description: "Modify or query the base name (last step) of path conforming to ${EL_PATH}"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2024-09-05 7:39:42 GMT (Thursday 5th September 2024)"
	revision: "16"

deferred class
	EL_PATH_BASE_NAME

inherit
	EL_MODULE_FORMAT

	EL_CHARACTER_32_CONSTANTS

feature -- Access

	base: ZSTRING
		-- last step in path

	base_name: ZSTRING
		-- `base' with the dot extension removed
		local
			index: INTEGER
		do
			index := dot_index
			if index > 0 then
				Result := base.substring (1, index - 1)
			else
				Result := base
			end
		ensure
			definition: old has_dot_extension implies base ~ dot.joined (Result, old extension)
		end

	extension: ZSTRING
			--
		local
			index: INTEGER
		do
			index := dot_index
			if index > 0 then
				Result := base.substring_end (index + 1)
			else
				create Result.make_empty
			end
		end

	version_interval: EL_SPLIT_ZSTRING_LIST
		-- `Result.item' is last natural number between two dots
		-- if `Result.off' then there is no interval
		do
			create Result.make (base, '.')
			from Result.finish until Result.before or else Result.item.is_natural loop
				Result.back
			end
		end

	version_number: INTEGER
			-- value of numeric value immediately before extension and separated by dots
			-- `-1' if no version number found

			-- Example: "myfile.02.mp3" returns 2
		do
			if attached version_interval as interval then
				if interval.off then
					Result := -1
				elseif attached base.substring (interval.item_lower, interval.item_upper) as number then
					number.prune_all_leading ('0')
					if number.is_empty then
						Result := 0
					elseif number.is_integer then
						Result := number.to_integer
					else
						Result := -1
					end
				end
			end
		end

feature -- Measurement

	dot_index: INTEGER
		-- index of last dot, 0 if none
		do
			if not base.is_empty then
				Result := base.last_index_of ('.', base.count)
			end
		end

feature -- Status Query

	base_matches (name: READABLE_STRING_GENERAL; case_insensitive: BOOLEAN): BOOLEAN
		-- `True' if `name' is same string as `base_name' if `case_insensitive' is `True'
		local
			pos_dot: INTEGER
		do
			if base.is_empty then
				Result := name.is_empty
			else
				pos_dot := dot_index
				if pos_dot > 0 then
					Result := pos_dot - 1 = name.count and then base.same_substring (name, 1, case_insensitive)
				else
					Result := base.count = name.count and then base.same_substring (name, 1, case_insensitive)
				end
			end
		ensure
			valid_result: not case_insensitive and Result implies base_name.same_string_general (name)
			valid_result: case_insensitive and Result implies base_name.same_caseless_characters_general (name, 1, name.count, 1)
		end

	has_dot_extension: BOOLEAN
		do
			Result := dot_index > 0
		end

	has_extension (a_extension: READABLE_STRING_GENERAL): BOOLEAN
		do
			Result := same_extension (a_extension, False)
		end

	has_some_extension (extension_list: ITERABLE [READABLE_STRING_GENERAL]; case_insensitive: BOOLEAN): BOOLEAN
		do
			Result := across extension_list as l_extension some
				same_extension (l_extension.item, case_insensitive)
			end
		end

	has_version_number: BOOLEAN
		do
			Result := not version_interval.off
		end

	same_base (a_base: READABLE_STRING_GENERAL): BOOLEAN
		do
			Result := base.same_string_general (a_base)
		end

	same_extension (a_extension: READABLE_STRING_GENERAL; case_insensitive: BOOLEAN): BOOLEAN
		local
			index: INTEGER
		do
			index := base.count - a_extension.count
			if index > 0 and then base [index] = '.' then
				if case_insensitive then
					Result := base.same_caseless_characters_general (a_extension, 1, a_extension.count, index + 1)
				else
					Result := base.same_characters_general (a_extension, 1, a_extension.count, index + 1)
				end
			end
		end

feature -- Element change

	add_extension (a_extension: READABLE_STRING_GENERAL)
		local
			str: ZSTRING
		do
			create str.make (base.count + a_extension.count + 1)
			str.append (base); str.append_character ('.'); str.append_string_general (a_extension)
			base := str
			reset_hash
		end

	remove_extension
		local
			index: INTEGER
		do
			index := dot_index
			if index > 0 then
				base.remove_tail (base.count - index + 1)
			end
		end

	rename_base (new_name: READABLE_STRING_GENERAL; preserve_extension: BOOLEAN)
			-- set new base to new_name, preserving extension if preserve_extension is True
		local
			l_extension: like extension
		do
			l_extension := extension
			base.wipe_out
			base.append_string_general (new_name)
			if preserve_extension and then not has_extension (l_extension) then
				add_extension (l_extension)
			end
			reset_hash
		end

	replace_extension (a_replacement: READABLE_STRING_GENERAL)
		local
			index: INTEGER
		do
			index := dot_index
			if index > 0 then
				base.replace_substring_general (a_replacement, index + 1, base.count)
			end
			reset_hash
		end

	set_base (a_base: READABLE_STRING_GENERAL)
		do
			if attached {like base} a_base as new then
				base := new
			else
				base.wipe_out
				base.append_string_general (a_base)
			end
			reset_hash
		end

	set_base_name (a_base_name: READABLE_STRING_GENERAL)
		do
			if has_dot_extension then
				base.replace_substring_general (a_base_name, 1, dot_index - 1)
				reset_hash
			else
				set_base (a_base_name)
			end
		ensure
			base_name_set: base_name.same_string_general (a_base_name)
		end

	set_version_number (number: like version_number)
		require
			has_version_number: has_version_number
		local
			digit_count: INTEGER; math: EL_INTEGER_MATH
		do
			if attached version_interval as interval and then not interval.off then
				digit_count := interval.item_count.max (Math.digit_count (number))
				if attached Format.zero_padded_integer (number, digit_count) as str then
					base.replace_substring_general (str, interval.item_lower, interval.item_upper)
				end
			end
			reset_hash
		end

feature {NONE} -- Deferred implementation

	reset_hash
		deferred
		end

end