class REGULAR_EXPRESSION_SEARCH_COMMAND

(source code)

description

Shell to search for regular expressions in source manifest files

note
	description: "Shell to search for regular expressions in source manifest files"
	to_do: "Escape any line with back quote character `' to fix gedit syntax highlighting"

	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-04 8:50:15 GMT (Thursday 4th April 2024)"
	revision: "25"

class
	REGULAR_EXPRESSION_SEARCH_COMMAND

inherit
	SOURCE_MANIFEST_COMMAND
		rename
			make as make_command,
			do_with_file as do_nothing_with
		export
			{ANY} read_manifest_files
		redefine
			execute, make_default
		end

	EL_MODULE_FILE_SYSTEM; EL_MODULE_COMMAND; EL_MODULE_DIRECTORY

	EL_MODULE_STRING_8; EL_MODULE_USER_INPUT

	EL_ENCODING_TYPE

	GREP_RESULT_CONSTANTS

create
	make

feature {EL_COMMAND_CLIENT} -- Initialization

	make (a_manifest_path_or_directory, output_path: FILE_PATH)
		do
			make_command (a_manifest_path_or_directory)
			if not output_path.is_empty then
				create output_file.make_with_path (output_path)
			end
		end

	make_default
		do
			Precursor
			create results_list.make (0)
			create grep_command.make
			create source_dir
			create source_class.make_empty
			output_file := Default_output_file
		end

feature -- Constants

	Description: STRING = "Searchs for a regular expression in Eiffel sources using grep command"

feature -- Basic operations

	execute
		require else
			source_trees_read: source_trees_read
		local
			user_quit, not_opened_result_file: BOOLEAN; count: INTEGER
			previous_options, grep_options: ZSTRING
		do
			lio.put_integer_field ("Manifest source file count", manifest.file_count)
			lio.put_new_line_x2
			lio.put_line ("TIPS")
			across Tip_list as tip loop
				lio.put_index_labeled_string (tip, Void, tip.item)
				lio.put_new_line
			end
			create grep_options.make_empty
			create previous_options.make_empty
			create user_quit

			from until user_quit loop
				grep_options := User_input.line ("Grep arguments")

				if grep_options.is_character ('%/27/') then
					user_quit := True

				elseif grep_options.count > 0 then
					grep_options.adjust
					if grep_options.is_character ('"') then
						if previous_options.count > 0 then
							grep_options := previous_options.twin
						end
					else
						previous_options := grep_options.twin
					end
					lio.put_new_line
					grep_command.set_options (grep_options)
					results_list.wipe_out

					search_sources

					lio.put_new_line
					count := results_list.count
					if has_error then
						lio.put_new_line
						grep_command.print_error ("grep")

					elseif count = 0 then
						lio.put_line ("No matches found")
					else
						if output_file = Default_output_file then
							results_list.do_all (agent write_result (?, ?, Void))
							lio.put_integer_field ("Matches found", count)
						else
							lio.put_path_field (Count_template #$ [count], output_file.path)
							output_file.open_write
							results_list.do_all (agent write_result (?, ?, output_file))
							output_file.close
							if not_opened_result_file then
								Command.launch_gedit (output_file.path)
								not_opened_result_file := True
							end
						end
						lio.put_new_line
					end
				end
			end
		end

feature {NONE} -- Implementation

	do_nothing_with (source_path: FILE_PATH)
		-- Unused
		do
		end

	iterate_source_trees
		do
			across manifest.source_tree_list as list until has_error loop
				grep_command.set_working_directory (list.item.dir_path)
				grep_command.execute
				if grep_command.has_error then
					has_error := True
				else
					across grep_command.lines as line loop
						results_list.extend (grep_command.working_directory, line.item)
					end
					Track.progress_listener.notify_tick
				end
			end
		end

	search_sources
		do
			has_error := False
			source_dir.wipe_out; source_class.wipe_out

			lio.put_labeled_substitution (
				"Searching", "%S in %S directories", [manifest.name, manifest.source_tree_list.count]
			)
			lio.put_new_line
			Track.progress (Console_display, manifest.source_tree_list.count, agent iterate_source_trees)

			if output_file /= Default_output_file then
				lio.put_new_line
			end
		end

	write_result (a_source_dir: DIR_PATH; line: ZSTRING; output: detachable EL_PLAIN_TEXT_FILE)
		local
			source_path: FILE_PATH; dir_line: detachable ZSTRING; index_colon, i: INTEGER
			class_line: ZSTRING
		do
			index_colon := 1
			from i := 1 until i > 2 or else index_colon = 0 loop
				index_colon := line.index_of (':', 1)
				if index_colon > 0 then
					line.replace_character ('`', '%'')
					if i = 1 then
						source_path := a_source_dir + line.substring (1, index_colon - 1)
						line.remove_head (index_colon)
					else
						line.prepend (space * (5 - index_colon))
					end
				end
				i := i + 1
			end

			if source_path.parent /~ source_dir then
				source_dir := source_path.parent
				dir_line := Comment.directory + source_dir
				if attached output as file then
					file.put_new_line
					file.put_line (dir_line)
				else
					lio.put_new_line
					lio.put_line (dir_line)
				end
			end
			if not source_path.base_matches (source_class, True) then
				source_class := source_path.base_name.as_upper
				class_line := Class_line_template #$ [source_class]
				if attached output as file then
					file.put_new_line
					file.put_line (class_line)
					file.put_new_line
				else
					lio.put_new_line
					lio.put_line (class_line)
					lio.put_new_line
				end
			end
			if attached output as file then
				file.put_line (line)
			else
				lio.put_line (line)
			end
		end

feature {NONE} -- Internal attributes

	grep_command: EIFFEL_GREP_COMMAND

	output_file: EL_PLAIN_TEXT_FILE

	results_list: EL_ARRAYED_MAP_LIST [DIR_PATH, ZSTRING]

	source_dir: DIR_PATH

	source_class: ZSTRING

	has_error: BOOLEAN

feature {NONE} -- Constants

	Count_template: ZSTRING
		once
			Result := "Writing %S results to"
		end

	Class_line_template: ZSTRING
		once
			Result := Class_keyword + " %S " + Comment.matching_lines
		end

	Default_output_file: EL_PLAIN_TEXT_FILE
		once
			create Result.make_with_name ("none.txt")
		end

	Tip_list: ARRAY [STRING]
		once
			Result := <<
				User_input.ESC_to_quit,
				"to repeat a search use ditto symbol %"",
				"-F for literal search (escaping $ as \$)"
			>>
		end

end