Fortran · 16908 bytes Raw Blame History
1 ! ==============================================================================
2 ! Module: config
3 ! Purpose: Shell configuration file handling (.fshrc)
4 ! ==============================================================================
5 module shell_config
6 use shell_types
7 use system_interface
8 use variables
9 use iso_fortran_env, only: input_unit, output_unit, error_unit
10 implicit none
11
12 ! Forward declaration to avoid circular dependency
13 abstract interface
14 subroutine parse_and_execute_interface(input_line, shell)
15 import :: shell_state_t
16 character(len=*), intent(in) :: input_line
17 type(shell_state_t), intent(inout) :: shell
18 end subroutine
19 end interface
20
21 procedure(parse_and_execute_interface), pointer :: parse_and_execute_proc => null()
22
23 contains
24
25 ! Main entry point - loads configs based on shell type
26 subroutine load_config_file(shell)
27 type(shell_state_t), intent(inout) :: shell
28
29 ! Check if this is first run and prompt for config creation
30 if (shell%is_interactive) then
31 call check_first_run_and_prompt(shell)
32 end if
33
34 if (shell%is_login_shell) then
35 ! Login shell: load profile files
36 call load_login_configs(shell)
37 else if (shell%is_interactive) then
38 ! Interactive non-login shell: load rc files
39 call load_interactive_configs(shell)
40 else
41 ! Non-interactive shell: check ENV variable
42 call load_noninteractive_configs(shell)
43 end if
44 end subroutine
45
46 ! Check if this is first run (no config files) and prompt user
47 subroutine check_first_run_and_prompt(shell)
48 type(shell_state_t), intent(inout) :: shell
49 character(len=MAX_PATH_LEN) :: home_dir
50 logical :: fortshrc_exists, fortsh_profile_exists
51 character(len=10) :: response
52 character(len=16) :: test_mode
53 integer :: stat
54
55 ! Skip first-run prompt in test mode
56 call get_environment_variable('FORTSH_TEST_MODE', test_mode, status=stat)
57 if (stat == 0 .and. len_trim(test_mode) > 0) return
58
59 if (.false.) print *, shell%cwd ! Silence unused warning - shell kept for future use
60
61 ! Get home directory using intrinsic
62 home_dir = ''
63 call get_environment_variable('HOME', home_dir)
64 if (len_trim(home_dir) == 0) return
65
66 ! Check if config files exist
67 inquire(file=trim(home_dir)//'/.fortshrc', exist=fortshrc_exists)
68 inquire(file=trim(home_dir)//'/.fortsh_profile', exist=fortsh_profile_exists)
69
70 ! If at least one exists, assume not first run
71 if (fortshrc_exists .or. fortsh_profile_exists) return
72
73 ! First run detected - prompt user
74 write(output_unit, '(a)') ''
75 write(output_unit, '(a)') '==================================================================='
76 write(output_unit, '(a)') 'Welcome to Fortran Shell (fortsh)!'
77 write(output_unit, '(a)') 'It looks like this is your first time running fortsh.'
78 write(output_unit, '(a)') ''
79 write(output_unit, '(a)') 'Would you like to create default configuration files?'
80 write(output_unit, '(a)') ' - ~/.fortshrc (interactive shell config)'
81 write(output_unit, '(a)') ' - ~/.fortsh_profile (login shell config)'
82 write(output_unit, '(a)') ' - ~/.fortsh_logout (logout script)'
83 write(output_unit, '(a)') '==================================================================='
84 write(output_unit, '(a)', advance='no') 'Create default configs? [Y/n]: '
85
86 ! Read user response
87 read(*, '(a)') response
88
89 ! Check response (default to yes)
90 if (len_trim(response) == 0 .or. &
91 response(1:1) == 'Y' .or. response(1:1) == 'y') then
92 write(output_unit, '(a)') ''
93 write(output_unit, '(a)') 'Creating default configuration files...'
94 call create_default_config()
95 write(output_unit, '(a)') ''
96 write(output_unit, '(a)') 'Configuration files created successfully!'
97 write(output_unit, '(a)') 'You can customize them by editing the files in your home directory.'
98 write(output_unit, '(a)') 'Type "config show" to view the current configuration.'
99 write(output_unit, '(a)') ''
100 else
101 write(output_unit, '(a)') ''
102 write(output_unit, '(a)') 'Skipping config creation.'
103 write(output_unit, '(a)') 'You can create them later by running: config create'
104 write(output_unit, '(a)') ''
105 end if
106 end subroutine
107
108 ! Load configuration for login shells
109 subroutine load_login_configs(shell)
110 type(shell_state_t), intent(inout) :: shell
111 character(len=MAX_PATH_LEN) :: home_dir
112 logical :: file_exists
113
114 ! 1. System-wide profile
115 call source_if_exists('/etc/fortsh/profile', shell, .false.)
116
117 ! 2. User profile (try in order)
118 home_dir = ''; call get_environment_variable('HOME', home_dir)
119 if (len(home_dir) > 0) then
120 ! Try ~/.fortsh_profile first
121 inquire(file=trim(home_dir)//'/.fortsh_profile', exist=file_exists)
122 if (file_exists) then
123 call source_if_exists(trim(home_dir)//'/.fortsh_profile', shell, .true.)
124 return
125 end if
126
127 ! Fall back to ~/.profile (POSIX compatibility)
128 call source_if_exists(trim(home_dir)//'/.profile', shell, .true.)
129 end if
130 end subroutine
131
132 ! Load configuration for interactive non-login shells
133 subroutine load_interactive_configs(shell)
134 type(shell_state_t), intent(inout) :: shell
135 character(len=MAX_PATH_LEN) :: home_dir, rc_file
136
137 ! Check for FORTSH_RC_FILE environment variable
138 rc_file = ''
139 call get_environment_variable('FORTSH_RC_FILE', rc_file)
140
141 if (len_trim(rc_file) > 0) then
142 ! Use specified rc file (or skip if /dev/null)
143 if (trim(rc_file) /= '/dev/null') then
144 call source_if_exists(trim(rc_file), shell, .true.)
145 end if
146 return
147 end if
148
149 ! 1. System-wide rc file
150 call source_if_exists('/etc/fortsh/fortshrc', shell, .false.)
151
152 ! 2. User rc file
153 home_dir = ''; call get_environment_variable('HOME', home_dir)
154 if (len(home_dir) > 0) then
155 ! Try ~/.fortshrc (new style)
156 call source_if_exists(trim(home_dir)//'/.fortshrc', shell, .true.)
157
158 ! Also try legacy ~/.fshrc for backward compatibility
159 call source_if_exists(trim(home_dir)//'/.fshrc', shell, .false.)
160 end if
161 end subroutine
162
163 ! Load configuration for non-interactive shells
164 subroutine load_noninteractive_configs(shell)
165 type(shell_state_t), intent(inout) :: shell
166 character(len=MAX_PATH_LEN) :: env_file
167
168 ! Check ENV variable
169 env_file = ''; call get_environment_variable('ENV', env_file)
170 if (len(env_file) > 0) then
171 call source_if_exists(env_file, shell, .false.)
172 end if
173 end subroutine
174
175 ! Helper: source a file if it exists
176 subroutine source_if_exists(filepath, shell, verbose)
177 character(len=*), intent(in) :: filepath
178 type(shell_state_t), intent(inout) :: shell
179 logical, intent(in) :: verbose
180 logical :: file_exists
181
182 inquire(file=filepath, exist=file_exists)
183 if (.not. file_exists) return
184
185 if (verbose) then
186 write(output_unit, '(a)') 'Loading ' // trim(filepath) // '...'
187 end if
188
189 ! Set up to source the file
190 shell%source_file = filepath
191 shell%should_source = .true.
192 end subroutine
193
194 ! Legacy load function for backward compatibility
195 subroutine load_legacy_config(shell)
196 type(shell_state_t), intent(inout) :: shell
197 character(len=MAX_PATH_LEN) :: home_dir, config_file
198 character(len=4096) :: line
199 integer :: unit, iostat
200 logical :: file_exists
201
202 ! Get home directory
203 home_dir = ''; call get_environment_variable('HOME', home_dir)
204 if (len(home_dir) == 0) then
205 return ! No HOME directory, skip config
206 end if
207
208 ! Construct config file path
209 config_file = trim(home_dir) // '/.fshrc'
210
211 ! Check if config file exists
212 inquire(file=config_file, exist=file_exists)
213 if (.not. file_exists) then
214 return ! No config file, continue normally
215 end if
216
217 ! Try to open and read the config file
218 open(newunit=unit, file=config_file, status='old', action='read', iostat=iostat)
219 if (iostat /= 0) then
220 write(error_unit, '(a)') 'fortsh: warning: could not read .fshrc'
221 return
222 end if
223
224 write(output_unit, '(a)') 'Loading .fshrc...'
225
226 ! Read and execute each line (simplified approach for now)
227 do
228 read(unit, '(a)', iostat=iostat) line
229 if (iostat /= 0) exit ! End of file or error
230
231 ! Skip empty lines and comments
232 line = adjustl(line)
233 if (len_trim(line) == 0 .or. line(1:1) == '#') cycle
234
235 ! For now, just execute simple variable assignments
236 if (index(line, '=') > 0 .and. index(line, ' ') == 0) then
237 call process_config_assignment(line, shell)
238 end if
239 end do
240
241 close(unit)
242 write(output_unit, '(a)') '.fshrc loaded successfully'
243 end subroutine
244
245 subroutine process_config_assignment(line, shell)
246 character(len=*), intent(in) :: line
247 type(shell_state_t), intent(inout) :: shell
248 integer :: eq_pos
249 character(len=256) :: var_name, var_value
250
251 eq_pos = index(line, '=')
252 if (eq_pos > 1) then
253 var_name = line(:eq_pos-1)
254 var_value = line(eq_pos+1:)
255
256 ! Set as shell variable
257 call set_shell_variable(shell, trim(var_name), trim(var_value))
258 end if
259 end subroutine
260
261 ! Create default configuration files
262 subroutine create_default_config()
263 character(len=MAX_PATH_LEN) :: home_dir
264
265 ! Get home directory
266 home_dir = ''; call get_environment_variable('HOME', home_dir)
267 if (len(home_dir) == 0) then
268 write(output_unit, '(a)') 'fortsh: warning: HOME not set, cannot create config files'
269 return
270 end if
271
272 ! Create all default config files
273 call create_fortshrc(home_dir)
274 call create_fortsh_profile(home_dir)
275 call create_fortsh_logout(home_dir)
276 end subroutine
277
278 ! Create default ~/.fortshrc
279 subroutine create_fortshrc(home_dir)
280 character(len=*), intent(in) :: home_dir
281 character(len=MAX_PATH_LEN) :: config_file
282 integer :: unit, iostat
283 logical :: file_exists
284
285 config_file = trim(home_dir) // '/.fortshrc'
286
287 inquire(file=config_file, exist=file_exists)
288 if (file_exists) then
289 write(output_unit, '(a)') 'fortsh: ~/.fortshrc already exists'
290 return
291 end if
292
293 open(newunit=unit, file=config_file, status='new', action='write', iostat=iostat)
294 if (iostat /= 0) then
295 write(error_unit, '(a)') 'fortsh: error: could not create ~/.fortshrc'
296 return
297 end if
298
299 ! Write comprehensive default configuration
300 write(unit, '(a)') '# ~/.fortshrc - Fortsh interactive shell configuration'
301 write(unit, '(a)') '# This file is sourced by interactive non-login shells'
302 write(unit, '(a)') ''
303 write(unit, '(a)') '# ===== Prompt Configuration ====='
304 write(unit, '(a)') '# Default: 2-line prompt with colors (zsh-style %F{color} or bash-style \[\e[...m\])'
305 write(unit, '(a)') '# Line 1: user@host :: path:branch status tracking venv'
306 write(unit, '(a)') '# Line 2: prompt character'
307 write(unit, '(a)') 'PS1=''%F{green}\u@\h%f :: %F{blue}\w%f%F{yellow}:\g%f %F{green}\G%f%F{cyan}\p%f \P'
308 write(unit, '(a)') '> '''
309 write(unit, '(a)') ''
310 write(unit, '(a)') '# RPROMPT: Right-side prompt (like zsh)'
311 write(unit, '(a)') 'RPROMPT=''%F{240}\A%f'''
312 write(unit, '(a)') ''
313 write(unit, '(a)') '# Prompt escape sequences:'
314 write(unit, '(a)') '# \u user \h host \w path \W basename \$ #/$ by uid'
315 write(unit, '(a)') '# \g git branch \G git status (checkmark/x/+) \p git up/down tracking'
316 write(unit, '(a)') '# \P venv name (.venv) \j jobs \! history# \# cmd#'
317 write(unit, '(a)') '# \t 24h time \T 12h time \d date \S epoch seconds'
318 write(unit, '(a)') ''
319 write(unit, '(a)') '# Alternative prompts (uncomment to use)'
320 write(unit, '(a)') '# Minimal: PS1=''\w> '''
321 write(unit, '(a)') '# Classic: PS1=''\u@\h :: \w > '''
322 write(unit, '(a)') '# No git: PS1=''%F{green}\u@\h%f :: %F{blue}\w%f'
323 write(unit, '(a)') '#> '''
324 write(unit, '(a)') ''
325 write(unit, '(a)') '# ===== Environment Variables ====='
326 write(unit, '(a)') 'export EDITOR=vim'
327 write(unit, '(a)') 'export PAGER=less'
328 write(unit, '(a)') ''
329 write(unit, '(a)') '# ===== Aliases ====='
330 write(unit, '(a)') 'alias ll=''ls -lah'''
331 write(unit, '(a)') 'alias la=''ls -A'''
332 write(unit, '(a)') 'alias ..=''cd ..'''
333 write(unit, '(a)') 'alias ...=''cd ../..'''
334 write(unit, '(a)') ''
335 write(unit, '(a)') '# ===== Shell Options ====='
336 write(unit, '(a)') '# set -o emacs # Emacs editing mode'
337
338 close(unit)
339 write(output_unit, '(a)') 'Created: ~/.fortshrc'
340 end subroutine
341
342 ! Create default ~/.fortsh_profile
343 subroutine create_fortsh_profile(home_dir)
344 character(len=*), intent(in) :: home_dir
345 character(len=MAX_PATH_LEN) :: config_file
346 integer :: unit, iostat
347 logical :: file_exists
348
349 config_file = trim(home_dir) // '/.fortsh_profile'
350
351 inquire(file=config_file, exist=file_exists)
352 if (file_exists) then
353 write(output_unit, '(a)') 'fortsh: ~/.fortsh_profile already exists'
354 return
355 end if
356
357 open(newunit=unit, file=config_file, status='new', action='write', iostat=iostat)
358 if (iostat /= 0) then
359 write(error_unit, '(a)') 'fortsh: error: could not create ~/.fortsh_profile'
360 return
361 end if
362
363 write(unit, '(a)') '# ~/.fortsh_profile - Fortsh login shell configuration'
364 write(unit, '(a)') '# This file is sourced by login shells'
365 write(unit, '(a)') ''
366 write(unit, '(a)') '# ===== PATH Configuration ====='
367 write(unit, '(a)') 'export PATH="$HOME/bin:$HOME/.local/bin:$PATH"'
368 write(unit, '(a)') ''
369 write(unit, '(a)') '# ===== Source interactive config if shell is interactive ====='
370 write(unit, '(a)') '# This ensures ~/.fortshrc is loaded in login shells too'
371 write(unit, '(a)') 'if [ -f ~/.fortshrc ]; then'
372 write(unit, '(a)') ' source ~/.fortshrc'
373 write(unit, '(a)') 'fi'
374 write(unit, '(a)') ''
375 write(unit, '(a)') '# ===== Login-specific setup ====='
376 write(unit, '(a)') '# Set umask'
377 write(unit, '(a)') '# umask 022'
378 write(unit, '(a)') ''
379 write(unit, '(a)') '# Display login information'
380 write(unit, '(a)') 'echo "Logged in as $USER on $HOSTNAME"'
381 write(unit, '(a)') 'echo "Today is $(date)"'
382
383 close(unit)
384 write(output_unit, '(a)') 'Created: ~/.fortsh_profile'
385 end subroutine
386
387 ! Create default ~/.fortsh_logout
388 subroutine create_fortsh_logout(home_dir)
389 character(len=*), intent(in) :: home_dir
390 character(len=MAX_PATH_LEN) :: config_file
391 integer :: unit, iostat
392 logical :: file_exists
393
394 config_file = trim(home_dir) // '/.fortsh_logout'
395
396 inquire(file=config_file, exist=file_exists)
397 if (file_exists) then
398 write(output_unit, '(a)') 'fortsh: ~/.fortsh_logout already exists'
399 return
400 end if
401
402 open(newunit=unit, file=config_file, status='new', action='write', iostat=iostat)
403 if (iostat /= 0) then
404 write(error_unit, '(a)') 'fortsh: error: could not create ~/.fortsh_logout'
405 return
406 end if
407
408 write(unit, '(a)') '# ~/.fortsh_logout - Fortsh logout script'
409 write(unit, '(a)') '# This file is executed when a login shell exits'
410 write(unit, '(a)') ''
411 write(unit, '(a)') '# ===== Cleanup tasks ====='
412 write(unit, '(a)') '# Clear the screen'
413 write(unit, '(a)') '# clear'
414 write(unit, '(a)') ''
415 write(unit, '(a)') '# Display logout message'
416 write(unit, '(a)') 'echo "Logging out from fortsh..."'
417 write(unit, '(a)') 'echo "Session ended at $(date)"'
418
419 close(unit)
420 write(output_unit, '(a)') 'Created: ~/.fortsh_logout'
421 end subroutine
422
423 ! Show the current config file content
424 subroutine show_config()
425 character(len=MAX_PATH_LEN) :: home_dir, config_file
426 character(len=4096) :: line
427 integer :: unit, iostat
428 logical :: file_exists
429
430 ! Get home directory
431 home_dir = ''; call get_environment_variable('HOME', home_dir)
432 if (len(home_dir) == 0) then
433 write(output_unit, '(a)') 'fortsh: warning: HOME not set'
434 return
435 end if
436
437 ! Construct config file path
438 config_file = trim(home_dir) // '/.fshrc'
439
440 ! Check if config file exists
441 inquire(file=config_file, exist=file_exists)
442 if (.not. file_exists) then
443 write(output_unit, '(a)') 'fortsh: no .fshrc file found'
444 return
445 end if
446
447 ! Open and display the config file
448 open(newunit=unit, file=config_file, status='old', action='read', iostat=iostat)
449 if (iostat /= 0) then
450 write(error_unit, '(a)') 'fortsh: error: could not read .fshrc'
451 return
452 end if
453
454 write(output_unit, '(a)') 'Contents of .fshrc:'
455 write(output_unit, '(a)') '=================='
456
457 do
458 read(unit, '(a)', iostat=iostat) line
459 if (iostat /= 0) exit
460 write(output_unit, '(a)') trim(line)
461 end do
462
463 close(unit)
464 end subroutine
465
466 end module shell_config