Fortran · 10071 bytes Raw Blame History
1 ! ==============================================================================
2 ! Module: aliases
3 ! Purpose: Shell alias functionality
4 ! ==============================================================================
5 module aliases
6 use shell_types
7 use iso_fortran_env, only: output_unit, error_unit
8 use io_helpers, only: write_stderr, write_stdout
9 implicit none
10
11 contains
12
13 subroutine set_alias(shell, alias_name, command)
14 type(shell_state_t), intent(inout) :: shell
15 character(len=*), intent(in) :: alias_name, command
16 integer :: i, empty_slot
17
18 empty_slot = -1
19
20 ! Look for existing alias or empty slot
21 do i = 1, size(shell%aliases)
22 if (trim(shell%aliases(i)%name) == trim(alias_name)) then
23 ! Update existing alias
24 shell%aliases(i)%command = command
25 return
26 else if (shell%aliases(i)%name(1:1) == char(0) .or. trim(shell%aliases(i)%name) == '') then
27 if (empty_slot == -1) empty_slot = i
28 end if
29 end do
30
31 ! Add new alias if there's space
32 if (empty_slot > 0) then
33 shell%aliases(empty_slot)%name = alias_name
34 shell%aliases(empty_slot)%command = command
35 shell%num_aliases = shell%num_aliases + 1
36 else
37 call write_stderr('alias: too many aliases defined')
38 end if
39 end subroutine
40
41 function get_alias(shell, alias_name) result(command)
42 type(shell_state_t), intent(in) :: shell
43 character(len=*), intent(in) :: alias_name
44 character(len=:), allocatable :: command
45 integer :: i
46 character(len=256) :: name_check
47
48 command = ''
49
50 do i = 1, size(shell%aliases)
51 name_check = shell%aliases(i)%name
52 ! Skip empty/unset aliases
53 if (len_trim(name_check) == 0) cycle
54 if (trim(name_check) == trim(alias_name)) then
55 command = trim(shell%aliases(i)%command)
56 return
57 end if
58 end do
59 end function
60
61 function unset_alias(shell, alias_name) result(found)
62 type(shell_state_t), intent(inout) :: shell
63 character(len=*), intent(in) :: alias_name
64 logical :: found
65 integer :: i
66
67 found = .false.
68
69 do i = 1, size(shell%aliases)
70 if (trim(shell%aliases(i)%name) == trim(alias_name)) then
71 shell%aliases(i)%name = ''
72 shell%aliases(i)%command = ''
73 shell%num_aliases = shell%num_aliases - 1
74 found = .true.
75 return
76 end if
77 end do
78
79 call write_stderr('unalias: ' // trim(alias_name) // ': not found')
80 end function
81
82 subroutine clear_all_aliases(shell)
83 type(shell_state_t), intent(inout) :: shell
84 integer :: i
85
86 do i = 1, size(shell%aliases)
87 shell%aliases(i)%name = ''
88 shell%aliases(i)%command = ''
89 end do
90 shell%num_aliases = 0
91 end subroutine
92
93 subroutine show_aliases(shell)
94 type(shell_state_t), intent(in) :: shell
95 integer :: i, count
96
97 count = 0
98 do i = 1, size(shell%aliases)
99 if (shell%aliases(i)%name(1:1) /= char(0) .and. trim(shell%aliases(i)%name) /= '') then
100 call write_stdout('alias ' // trim(shell%aliases(i)%name) // &
101 '=' // "'" // trim(shell%aliases(i)%command) // "'")
102 count = count + 1
103 end if
104 end do
105
106 ! POSIX: alias with no args and no aliases produces no output (bash behavior)
107 ! if (count == 0) then
108 ! write(output_unit, '(a)') 'No aliases defined'
109 ! end if
110 end subroutine
111
112 function expand_alias_with_params(shell, alias_name, args, num_args) result(expanded_command)
113 type(shell_state_t), intent(in) :: shell
114 character(len=*), intent(in) :: alias_name
115 character(len=*), intent(in) :: args(:)
116 integer, intent(in) :: num_args
117 character(len=2048) :: expanded_command
118
119 character(len=:), allocatable :: alias_command
120 character(len=2048) :: work_command
121 integer :: pos, param_start, param_end, param_num
122 character(len=16) :: param_str
123 character(len=256) :: replacement
124
125 ! Get the alias command
126 alias_command = ''
127 call get_alias_command(shell, alias_name, alias_command)
128 if (len_trim(alias_command) == 0) then
129 expanded_command = ''
130 return
131 end if
132
133 work_command = alias_command
134 expanded_command = ''
135 pos = 1
136
137 ! Process parameter substitutions
138 do while (pos <= len_trim(work_command))
139 if (work_command(pos:pos) == '$' .and. pos < len_trim(work_command)) then
140 param_start = pos + 1
141
142 ! Check for different parameter formats
143 if (work_command(param_start:param_start) == '{') then
144 ! ${n} format
145 param_start = param_start + 1
146 param_end = param_start
147 do while (param_end <= len_trim(work_command) .and. work_command(param_end:param_end) /= '}')
148 param_end = param_end + 1
149 end do
150
151 if (param_end <= len_trim(work_command) .and. work_command(param_end:param_end) == '}') then
152 param_str = work_command(param_start:param_end-1)
153 pos = param_end + 1
154 else
155 expanded_command = trim(expanded_command) // '$'
156 pos = pos + 1
157 cycle
158 end if
159 else if (work_command(param_start:param_start) >= '0' .and. work_command(param_start:param_start) <= '9') then
160 ! $n format
161 param_end = param_start
162 do while (param_end <= len_trim(work_command) .and. &
163 work_command(param_end:param_end) >= '0' .and. work_command(param_end:param_end) <= '9')
164 param_end = param_end + 1
165 end do
166 param_str = work_command(param_start:param_end-1)
167 pos = param_end
168 else if (work_command(param_start:param_start) == '*') then
169 ! $* - all parameters
170 replacement = ''
171 do param_num = 1, num_args
172 if (param_num > 1) replacement = trim(replacement) // ' '
173 replacement = trim(replacement) // trim(args(param_num))
174 end do
175 expanded_command = trim(expanded_command) // trim(replacement)
176 pos = param_start + 1
177 cycle
178 else if (work_command(param_start:param_start) == '@') then
179 ! $@ - all parameters (same as $* for aliases)
180 replacement = ''
181 do param_num = 1, num_args
182 if (param_num > 1) replacement = trim(replacement) // ' '
183 replacement = trim(replacement) // trim(args(param_num))
184 end do
185 expanded_command = trim(expanded_command) // trim(replacement)
186 pos = param_start + 1
187 cycle
188 else if (work_command(param_start:param_start) == '#') then
189 ! $# - number of parameters
190 write(replacement, '(I0)') num_args
191 expanded_command = trim(expanded_command) // trim(replacement)
192 pos = param_start + 1
193 cycle
194 else
195 expanded_command = trim(expanded_command) // '$'
196 pos = pos + 1
197 cycle
198 end if
199
200 ! Convert parameter string to number
201 read(param_str, *, iostat=param_end) param_num
202 if (param_end == 0 .and. param_num >= 0 .and. param_num <= num_args) then
203 if (param_num == 0) then
204 ! $0 is the alias name itself
205 replacement = alias_name
206 else if (param_num <= num_args) then
207 replacement = args(param_num)
208 else
209 replacement = ''
210 end if
211 expanded_command = trim(expanded_command) // trim(replacement)
212 else
213 expanded_command = trim(expanded_command) // '$' // trim(param_str)
214 end if
215 else
216 expanded_command = trim(expanded_command) // work_command(pos:pos)
217 pos = pos + 1
218 end if
219 end do
220
221 ! If no parameters were used, append all arguments at the end
222 if (index(alias_command, '$') == 0 .and. num_args > 0) then
223 do param_num = 1, num_args
224 expanded_command = trim(expanded_command) // ' ' // trim(args(param_num))
225 end do
226 end if
227 end function
228
229 subroutine get_alias_command(shell, alias_name, command)
230 type(shell_state_t), intent(in) :: shell
231 character(len=*), intent(in) :: alias_name
232 character(len=:), allocatable, intent(out) :: command
233
234 integer :: i
235
236 command = ''
237 do i = 1, size(shell%aliases)
238 if (trim(shell%aliases(i)%name) == trim(alias_name)) then
239 if (allocated(shell%aliases(i)%command)) then
240 command = shell%aliases(i)%command
241 end if
242 return
243 end if
244 end do
245 end subroutine
246
247 function is_alias(shell, name) result(found)
248 type(shell_state_t), intent(in) :: shell
249 character(len=*), intent(in) :: name
250 logical :: found
251 integer :: i
252 character(len=256) :: alias_name
253
254 found = .false.
255 do i = 1, size(shell%aliases)
256 alias_name = shell%aliases(i)%name
257 ! Skip empty/unset aliases
258 if (len_trim(alias_name) == 0) cycle
259 if (trim(alias_name) == trim(name)) then
260 found = .true.
261 return
262 end if
263 end do
264 end function
265
266 ! Expand alias in a command line
267 subroutine expand_alias(shell, input_line, expanded_line)
268 type(shell_state_t), intent(in) :: shell
269 character(len=*), intent(in) :: input_line
270 character(len=:), allocatable, intent(out) :: expanded_line
271
272 character(len=256) :: first_word, rest_of_line
273 character(len=:), allocatable :: alias_command
274 integer :: space_pos
275
276 expanded_line = input_line
277
278 ! POSIX: Aliases are only expanded in interactive shells
279 if (.not. shell%is_interactive) return
280
281 ! Extract first word
282 space_pos = index(trim(input_line), ' ')
283 if (space_pos > 0) then
284 first_word = input_line(:space_pos-1)
285 rest_of_line = input_line(space_pos:)
286 else
287 first_word = trim(input_line)
288 rest_of_line = ''
289 end if
290
291 ! Check if first word is an alias
292 if (is_alias(shell, first_word)) then
293 alias_command = get_alias(shell, first_word)
294 if (len(alias_command) > 0) then
295 expanded_line = trim(alias_command) // trim(rest_of_line)
296 end if
297 end if
298 end subroutine
299
300 end module aliases