class EIFFEL_CLASS

(source code)

description

Class to render github like markdown found in the description note field of Eiffel classes.

note
	description: "[
		Class to render github like markdown found in the description note field of Eiffel classes.
	]"

	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-04-07 7:44:31 GMT (Sunday 7th April 2024)"
	revision: "58"

class
	EIFFEL_CLASS

inherit
	EL_FILE_SYNC_ITEM
		rename
			make as make_sync_item,
			source_path as html_source_path
		undefine
			is_equal, copy
		redefine
			is_modified, sink_content
		end

	EVOLICITY_SERIALIZEABLE
		rename
			output_path as html_output_path
		undefine
			is_equal
		redefine
			make_default, serialize, copy
		end

	COMPARABLE undefine copy end

	EL_THREAD_ACCESS [CODEBASE_METRICS] undefine is_equal, copy end

	EL_EIFFEL_KEYWORDS

	EL_MODULE_DIRECTORY; EL_MODULE_FILE; EL_MODULE_XML

	PUBLISHER_CONSTANTS; EL_ZSTRING_CONSTANTS; EL_CHARACTER_32_CONSTANTS

	SHARED_CODEBASE_METRICS

	EL_SHARED_ZSTRING_BUFFER_SCOPES

create
	make

feature {NONE} -- Initialization

	make (a_source_path: like source_path; a_library_ecf: like library_ecf; a_repository: like repository)
			--
		local
			source_text: STRING; utf: EL_UTF_CONVERTER
		do
			relative_source_path := a_source_path.relative_path (a_repository.root_dir)
			make_from_template_and_output (
				a_repository.templates.eiffel_source,
				a_repository.output_dir + relative_source_path.with_new_extension (Html)
			)
			library_ecf := a_library_ecf; repository := a_repository; source_path := a_source_path
			name := source_path.base_name.as_upper
			source_text := File.plain_text (source_path)
			code_text := new_code_text (source_text)
			make_sync_item (
				repository.output_dir, repository.ftp_host, html_output_path.relative_path (repository.output_dir), 0
			)
			create notes.make (relative_source_path.parent, a_repository.note_fields)

			if attached restricted_access (Codebase_metrics) as metrics then
				if utf.is_utf_8_file (source_text) then
					metrics.add_source (source_text, Utf_8)
				else
					metrics.add_source (source_text, Latin_1)
				end
				end_restriction
			end
		end

	make_default
		do
			create source_path
			create name.make_empty
			notes := Default_notes
			Precursor
		end

feature -- Access

	class_text: ZSTRING
		do
			Result := XML.escaped (code_text.substring_end (class_begin_index + 1))
		end

	code_text: ZSTRING

	name: ZSTRING

	notes: EIFFEL_NOTES

	notes_text: ZSTRING
		do
			Result := XML.escaped (code_text.substring (1, class_begin_index))
		end

	relative_html_path: FILE_PATH
		-- html path relative to `library_ecf.ecf_dir'
		do
			Result := source_path.relative_dot_path (library_ecf.ecf_path).with_new_extension (Html)
		end

	relative_source_path: FILE_PATH

	source_path: FILE_PATH

feature -- Status report

	has_class_name (a_name: ZSTRING): BOOLEAN
		local
			pos_name: INTEGER; c_left, c_right: CHARACTER_32
			l_text: like code_text
		do
			l_text := code_text
			from pos_name := 1 until Result or pos_name = 0 loop
				pos_name := l_text.substring_index (a_name, pos_name)
				if pos_name > 0 then
					c_left := l_text.item (pos_name - 1)
					c_right := l_text.item (pos_name + a_name.count)
					if (c_left.is_alpha or c_left = '_') or else (c_right.is_alpha or c_right = '_') then
						pos_name := (pos_name + a_name.count).min (l_text.count)
					else
						Result := True
					end
				end
			end
		end

	has_further_information: BOOLEAN
		do
			Result := not further_information_fields.is_empty
		end

	is_example: BOOLEAN
		do
			Result := not is_library
		end

	is_modified: BOOLEAN
		do
			Result := previous_digest /= current_digest or else not html_output_path.exists
		end

	is_source_modified: BOOLEAN
		-- `True' if file was modified since creation of `Current'
		do
			if attached crc_generator as crc then
				crc.add_string (new_code_text (File.plain_text (source_path)))
				crc.add_string (relative_source_path)
				if initial_current_digest.to_boolean then
					Result := initial_current_digest /= crc.checksum
				else
					Result := current_digest /= crc.checksum
				end
			end
		end

	is_library: BOOLEAN
		do
		end

	notes_filled: BOOLEAN

feature -- Basic operations

	check_class_references
		do
			fill_notes
			notes.check_class_references (source_path.base)
		end

	fill_notes
		do
			notes.fill (source_path)
			notes_filled := True
		end

	serialize
		do
			if not notes_filled then
				fill_notes
			end
			Precursor
		end

	sink_source_substitutions
		-- sink the values of ${<type-name>} occurrences `code_text'. Eg. ${CLASS_NAME}
		local
			crc: like crc_generator
		do
			crc := crc_generator
			if initial_current_digest.to_boolean then
				current_digest := initial_current_digest
			else
				initial_current_digest := current_digest
			end
			crc.set_checksum (current_digest)
			Class_link_list.add_to_crc (crc, code_text)
			current_digest := crc.checksum
		end

feature -- Comparison

	is_less alias "<" (other: like Current): BOOLEAN
			-- Is current object less than `other'?
		do
			if notes.has_description = other.notes.has_description then
				if name ~ other.name then
					-- Needed to get a consistent `current_digest' in `LIBRARY_CLASS'
					Result := relative_source_path < other.relative_source_path
				else
					Result := name < other.name
				end

			else
				Result := notes.has_description
			end
		end

feature {NONE} -- Implementation

	class_begin_index: INTEGER
		do
			across Class_begin_strings as string until Result > 0 loop
				Result := code_text.substring_index (string.item, 1)
			end
		end

	copy (other: like Current)
		do
			standard_copy (other)
			notes := other.notes.twin
		end

	further_information: ZSTRING
			-- other information besides the description
		local
			pos_comma: INTEGER
		do
			Result := further_information_fields.joined_with_string (", ")
			if not Result.is_empty then
				pos_comma := Result.last_index_of (',', Result.count)
				if pos_comma > 0 then
					Result.replace_substring_general (" and", pos_comma, pos_comma)
				end
				Result.to_lower
			end
		end

	further_information_fields: EL_ZSTRING_LIST
		do
			Result := notes.other_field_titles
		end

	new_code_text (raw_source: STRING): ZSTRING
		local
			utf: EL_UTF_CONVERTER
		do
			if utf.is_utf_8_file (raw_source) then
				create Result.make_from_utf_8 (utf.bomless_utf_8 (raw_source))
			else
				Result := raw_source
			end
		end

	relative_ecf_html_path: ZSTRING
		do
			Result := library_ecf.html_index_path.relative_dot_path (relative_source_path)
		end

	sink_content (crc: like crc_generator)
		do
			crc.add_string (code_text)
			crc.add_string (relative_source_path)
		end

feature {NONE} -- Internal attributes

	library_ecf: EIFFEL_CONFIGURATION_FILE

	repository: REPOSITORY_PUBLISHER

	initial_current_digest: NATURAL
		-- `current_digest' before modification by `sink_source_substitutions'

feature {NONE} -- Evolicity fields

	getter_function_table: like getter_functions
			--
		do
			create Result.make (<<
				["description_elements",	agent: like notes.description_elements do Result := notes.description_elements end],
				["note_fields",				agent: like notes.field_list do Result := notes.field_list end],

				["has_description",			agent: BOOLEAN_REF do Result := notes.has_description.to_reference end],
				["has_further_information",agent: BOOLEAN_REF do Result := has_further_information.to_reference end],
				["has_fields",					agent: BOOLEAN_REF do Result := notes.has_fields.to_reference end],
				["is_library", 				agent: BOOLEAN_REF do Result := is_library.to_reference end],

				["notes_text", 				agent notes_text],
				["class_text", 				agent class_text],
				["further_information",		agent further_information],
				["ecf_contents_path", 		agent relative_ecf_html_path],

				["name", 						agent: STRING do Result := name.string end],
				["name_as_lower", 			agent: STRING do Result := name.string.as_lower end],
				["html_path", 					agent: ZSTRING do Result := relative_html_path end],
				["favicon_markup_path", 	agent: ZSTRING do Result := repository.templates.favicon_markup_path end],
				["top_dir", 					agent: ZSTRING do Result := Directory.relative_parent (relative_source_path.step_count - 1) end],

				["relative_dir", 				agent: DIR_PATH do Result := relative_source_path.parent end],
				["source_path", 				agent: FILE_PATH do Result := relative_source_path end]
			>>)
		end

feature {NONE} -- Constants

	Class_begin_strings: EL_ZSTRING_LIST
		once
			create Result.make (3)
			across Class_declaration_keywords as l_word loop
				Result.extend (new_line * 1 + l_word.item)
			end
		end

	Default_notes: EIFFEL_NOTES
		once
			create Result.make_default
		end

	Template: STRING = ""

end