class EL_FILE_GENERAL_LINE_SOURCE

(source code)

description

Interface for object that interates over the lines of an file object conforming to PLAIN_TEXT_FILE. The line items conform to STRING_GENERAL

note
	description: "[
		Interface for object that interates over the lines of an file object conforming to ${PLAIN_TEXT_FILE}.
		The line items conform to ${STRING_GENERAL}
	]"

	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-18 9:28:47 GMT (Wednesday 18th September 2024)"
	revision: "11"

deferred class
	EL_FILE_GENERAL_LINE_SOURCE [S -> STRING_GENERAL create make end]

inherit
	EL_LINEAR [S]
		rename
			item as item_copy
		redefine
			extend_special
		end

	ITERABLE [S]

	EL_ENCODEABLE_AS_TEXT
		rename
			make as make_encodeable
		redefine
			make_default
		end

	EL_MODULE_ENCODING
		rename
			Encoding as Encoding_
		end

	EL_MODULE_File
		rename
			File as File_
		end

	EL_EVENT_LISTENER
		rename
			notify as on_encoding_update
		end

	EL_CHARACTER_32_CONSTANTS

feature {NONE} -- Initialization

	make (a_file: like Default_file)
		do
			make_default
			set_file (a_file)
			if a_file.exists then
				check_encoding
			end
			is_file_external := True
		end

	make_default
			--
		do
			Precursor
			create shared_item.make (0)
			file := default_file
			on_encoding_change.add_listener (Current)
		end

feature -- Measurement

	bom_count: INTEGER
		-- byte order mark count

	traversed_count: INTEGER
		-- count of line traversed by `start' or `forth'

	index: INTEGER

	item_count: INTEGER
		do
			Result := shared_item.count
		end

feature -- Access

	item_copy: S
		do
			Result := shared_item.twin
		end

	shared_item: S
		-- line item that is updated for each line read in `update_item'
		-- Use `item_copy' if keeping a reference to the line item

feature -- Conversion

	as_list: like new_list
		do
			start
			if file.readable then
				Result := new_list (file.count // File_.average_line_count_of (file))
				file.move (- {PLATFORM}.is_windows.to_integer) -- workaround for Windows bug
				from until after loop
					Result.extend (shared_item.twin)
					forth
				end
			else
				create Result.make_empty
			end
		end

feature -- Access

	joined: S
		do
			create Result.make (file.count)
			from start until after loop
				if index > 1 then
					Result.append_code ({EL_ASCII}.Newline)
				end
				Result.append (shared_item)
				forth
			end
		end

	new_cursor: EL_LINE_SOURCE_ITERATION_CURSOR [S]
			--
		do
			create Result.make (Current)
			Result.start
		end

feature -- Status query

	after: BOOLEAN
			-- Is there no valid position to the right of current one?
		do
			Result := index = traversed_count + 1
		end

	is_closed: BOOLEAN
			--
		do
			Result := file.is_closed
		end

	is_empty: BOOLEAN
			-- Is there no element?
		do
			Result := attached file implies file.is_empty
		end

	is_file_external: BOOLEAN
		-- True if file is managed externally to object

	is_open: BOOLEAN
			--
		do
			Result := file.is_open_read
		end

feature -- Output

	print_first (log: EL_LOGGABLE; n: INTEGER)
		-- print first `n' lines to `log' output with leading tabs expanded to 3 spaces
		local
			line: ZSTRING; tab_count: INTEGER
		do
			across Current as ln until ln.cursor_index > n loop
				create line.make_from_general (ln.shared_item)
				tab_count := line.leading_occurrences ('%T')
				if tab_count > 0 then
					line.replace_substring (space * (tab_count * 3), 1, tab_count)
				end
				log.put_line (line)
			end
			if not after then
				log.put_line (dot * 2)
			end
		end

feature -- Cursor movement

	forth
		-- Move to next position
		require else
			file_is_open: is_open
		local
			found_item: BOOLEAN
		do
			if attached file as f then
				if not f.end_of_file then
					read_line (f)
					if f.end_of_file then
						found_item := f.last_string.count > 0
					else
						found_item := True
					end
				end
				if found_item then
					update_item
					traversed_count := traversed_count + 1
				end
				index := index + 1
				if after and not is_file_external then
					f.close
				end
			end
		ensure then
			closed_if_eof: after and not is_file_external implies file.is_closed
		end

	start
		-- Move to first position if any.
		do
			if file = default_file then
				index := 1
				traversed_count := 0
			else
				open_at_start
				traversed_count := 0
				if file.off then
					index := 1
					shared_item.keep_head (0)
				else
					index := 0
					forth
				end
			end
		end

feature -- Status setting

	close
			--
		do
			if file.is_open_read then
				file.close
			end
		end

	open_at_start
		do
			if not file.is_open_read then
				file.open_read
			end
			file.go (bom_count)
		end

feature -- Basic operations

	delete_file
			--
		do
			if file.is_open_read then
				file.close
			end
			file.delete
		end

feature {NONE} -- Implementation

	check_encoding
		do
			if attached {EL_STRING_IO_MEDIUM} file as medium then
				set_encoding (medium.encoding)

			elseif not encoding_detected and then attached Encoding_.file_info (file) as info then
				bom_count := info.bom_count
				encoding_detected := info.detected
				if encoding_detected then
					set_encoding (info.encoding)
				end
			end
		end

	extend_special (item: S; area: SPECIAL [S])
		-- fixes `to_array' and `to_special'
		do
			area.extend (item.twin)
		end

	finish
			-- Move to last position.
		do
		end

	new_list (n: INTEGER): EL_STRING_LIST [S]
		do
			create Result.make (n)
		end

	read_line (f: like Default_file)
		do
			f.read_line
		end

	set_file (a_file: like Default_file)
		do
			encoding_detected := False
			file := a_file
		end

feature {NONE} -- Deferred

	on_encoding_update
		deferred
		end

	update_item
		deferred
		end

feature {NONE} -- Internal attributes

	encoding_detected: BOOLEAN

	file: like default_file

feature {NONE} -- Constants

	Default_file: PLAIN_TEXT_FILE
		once
			create Result.make_with_name ("default.txt")
		end

end