class EL_CONSOLE_LOG_OUTPUT

(source code)

description

Console log output

note
	description: "Console log output"

	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: "2023-12-21 9:44:05 GMT (Thursday 21st December 2023)"
	revision: "39"

class
	EL_CONSOLE_LOG_OUTPUT

inherit
	ANY

	EL_MODULE_CONSOLE; EL_MODULE_ENVIRONMENT

	EL_SHARED_STRING_POOLS; EL_SHARED_FORMAT_FACTORY

	EL_LOGGABLE_CONSTANTS; EL_STRING_8_CONSTANTS; EL_CHARACTER_8_CONSTANTS

create
	make

feature -- Initialization

	make
		do
			create buffer.make (30)
			create buffer_indices.make (30)
			create index_label_table.make (7)
			create new_line_prompt.make_from_string ("%N")
			string_pool := Shared_string_8_pool
			std_output := io.Output
		end

feature -- Status change

	restore (previous_stack_count: INTEGER)
			--
		do
			tab_repeat_count := previous_stack_count
		end

	set_tab_plus (offset: INTEGER)
			--
		do
			tab_repeat_count := tab_repeat_count.item + offset
		end

	tab_left
			--
		do
			set_tab_plus (-1)
		end

	tab_right
			--
		do
			set_tab_plus (1)
		end

	set_text_color (code: INTEGER)
		require
			valid_code: valid_colors.has (code)
		do
		end

	set_text_color_light (code: INTEGER)
		require
			valid_code: valid_colors.has (code)
		do
		end

feature -- Output

	put_boolean (b: BOOLEAN)
			--
		do
			extended_buffer_last (5).append_boolean (b)
		end

	put_character (c: CHARACTER)
			--
		do
			extended_buffer_last (1).append_character (c)
		end

	put_classname (a_name: READABLE_STRING_8)
		do
			set_text_color_light (Color.Blue)
			buffer.extend (a_name)
			set_text_color (Color.Default)
		end

	put_index_label (indexable: ANY; label_or_format: detachable READABLE_STRING_GENERAL)
		-- output integer index value associated with `indexable' object that may conform to one of:
		--		`LINEAR', `INDEXABLE_ITERATION_CURSOR', `INTEGER_32_REF', `NATURAL_32_REF'

		-- An optional formatting `label_or_format' that may be interpreted in the following ways:

		--		1. A template if it contains a substitution placeholder '%S' for the `indexable' value (Eg. "i_th [%S]")
		--		2. A padding format for the `indexable' value if all the characters are equal to '9'
		--		3. Or else a prefix before the `indexable' value
		require
			is_indexable: is_indexable (indexable)
		local
			substitution_index, index, width: INTEGER;
			leading, trailing: READABLE_STRING_GENERAL; math: EL_INTEGER_MATH
		do
			if attached {INDEXABLE_ITERATION_CURSOR [ANY]} indexable as list then
				index := list.cursor_index

			elseif attached {LINEAR [ANY]} indexable as list then
				index := list.index

			elseif attached {INTEGER_32_REF} indexable as integer then
				index := integer.item

			elseif attached {NATURAL_32_REF} indexable as natural then
				index := natural.item.to_integer_32
			end
			leading := Empty_string_8; trailing := Empty_string_8
			if attached label_or_format as str then
				if str.occurrences ('9') = str.count then
					width := math.digits (index)
					if width < str.count then
					-- create padding to right justify
						leading := space * (str.count - width)
					end

				elseif attached index_label_table as table then
					if table.has_key (str) and then attached table.found_item as pair then
						leading := pair.leading; trailing := pair.trailing
					else
						substitution_index := str.index_of ('%S', 1)
						if substitution_index > 0 then
						-- split for example: "i_th [%S]"
							leading := str.substring (1, substitution_index - 1) -- "i_th [
							trailing := str.substring (substitution_index + 1, str.count) -- "]"
						else
							leading := str
						end
						table.extend ([leading, trailing], str)
					end
				end
			end
			set_text_color_light (Color.Purple)
			if leading.count > 0 then
				put_string_general (leading)
			end
			extended_buffer_last (11).append_integer (index)
			if trailing.count > 0 then
				put_string_general (trailing)
			end
			set_text_color (Color.Default)
			put_string (Dot_space)
		end

	put_keyword (keyword: READABLE_STRING_8)
		do
			set_text_color_light (Color.Red)
			buffer.extend (keyword)
			set_text_color (Color.Default)
		end

	put_label (a_name: READABLE_STRING_GENERAL)
		do
			set_text_color_light (Color.Purple)
			put_string_general (a_name)
			set_text_color (Color.Default)
			put_string (Colon_space)
		end

	put_lines (lines: LIST [ZSTRING])
			--
		do
			from lines.start until lines.off loop
				set_text_color (Color.Yellow)
				buffer.extend (lines.item)
				set_text_color (Color.Default)
				lines.forth
				if not lines.after then
					put_new_line
				end
			end
		end

	put_new_line
			-- Add a string to the buffer
		local
			i: INTEGER
		do
			buffer.extend (new_line_prompt)
			from
				i := 1
			until
				i > tab_repeat_count
			loop
				buffer.extend (Tab_string)
				i := i + 1
			end
		end

	put_path (a_path: EL_PATH)
			--
		do
			buffer.extend (a_path.to_string)
		end

	put_quoted_string (a_str: READABLE_STRING_GENERAL; quote_mark: STRING)
		do
			set_text_color (Color.Yellow)
			put_string (quote_mark)
			put_string_general (a_str)
			put_string (quote_mark)
			set_text_color (Color.Default)
		end

	put_separator
		do
			buffer.extend (Line_separator)
			put_new_line
		end

	put_string (s: STRING)
		do
			buffer.extend (s)
		end

	put_string_general (s: READABLE_STRING_GENERAL)
			--
		do
			buffer.extend (s)
		end

feature -- Numeric output

	put_double (d: DOUBLE; a_format: detachable STRING)
			--
		do
		-- Using `extended_buffer_last' not more efficient
			if attached a_format as str then
				buffer.extend (Format.double_as_string (d, str))
			else
				buffer.extend (d.out)
			end
		end

	put_integer (i: INTEGER)
			-- Add a string to the buffer
		do
			extended_buffer_last (11).append_integer (i)
		end

	put_natural (n: NATURAL)
		do
			extended_buffer_last (10).append_natural_32 (n)
		end

	put_real (r: REAL; a_format: detachable STRING)
			--
		do
		-- Using `extended_buffer_last' not more efficient
			if attached a_format as l_format then
				buffer.extend (Format.double_as_string (r, l_format))
			else
				buffer.extend (r.out)
			end
		end

feature -- Basic operations

	clear
		do
		end

	move_cursor_up (n: INTEGER)
		-- move cursor up `n' lines (Linux only)
		do
		end

	flush
		-- Write contents of buffer to file if it is free (not locked by another thread)
		-- Return strings of type {STRING_32} to recyle pool
		do
			buffer.do_all (agent flush_string_general)
			buffer.wipe_out
			string_pool.free_list (buffer_indices)
		end

feature {NONE} -- Implementation

	extended_buffer_last (size: INTEGER): STRING_8
		do
			Result := string_pool.borrowed_item (size)
			buffer_indices.extend (string_pool.last_index)
			buffer.extend (Result)
		end

	flush_string_8 (str_8: READABLE_STRING_8)
		do
			write_console (str_8)
		end

	flush_string_general (str: READABLE_STRING_GENERAL)
		do
			if str.is_string_8 and then attached {READABLE_STRING_8} str as str_8 then
				flush_string_8 (str_8)
			else
				write_console (str)
			end
		end

	write_console (str: READABLE_STRING_GENERAL)
		do
			std_output.put_string (Console.encoded (str, False))
		end

feature {NONE} -- Internal attributes

	buffer: ARRAYED_LIST [READABLE_STRING_GENERAL]

	buffer_indices: ARRAYED_LIST [INTEGER]

	index_label_table: STRING_TABLE [TUPLE [leading, trailing: READABLE_STRING_GENERAL]]
		-- substitution parts
		-- Eg. "i_th [%S]" => ["i_th [", "]"]

	new_line_prompt: STRING

	std_output: PLAIN_TEXT_FILE

	string_pool: EL_STRING_POOL [STRING]
		-- recycled strings

	tab_repeat_count: INTEGER

feature {EL_LOGGABLE} -- Constants

	Colon_space: STRING = ": "

	Dot_space: STRING = ". "

	Line_separator: STRING
		once
			create Result.make_filled ('-', 100)
		end

	Tab_string: STRING = "  "

	Tail_character_count : INTEGER = 1500

end