ruby-shell 1.2 → 2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rsh +151 -134
  3. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d3646331db7eac6ea26f07f0878354ca9b3ed9f46ebda4e2bd177d252ce67d3
4
- data.tar.gz: deffdb053c7284ed35110c482a30adfd0ec732ce5eb0d2ea4188638cf92d1b0e
3
+ metadata.gz: 4a665db64b41e710d9518377b67c43fa024c06267b532e5fe055060d6ab72b02
4
+ data.tar.gz: c9a28e30899b31d7905a8a16efd38bcadc33297cf77f43b08a2eafcfb00aaa64
5
5
  SHA512:
6
- metadata.gz: 6dec4e805bf977dd4de28784a1e1bc4c71c7df0c865c73e479e54d053de0743b9592c4c1d331e321da23d1c1e4136709fd7fb269acb372dac50d4aa3f1196737
7
- data.tar.gz: d50d00d2f0f9af08c1516941a808087936d512f43971eb2374b476c295137048c95bb0890960589dfdc35b9b402147d0c9d162ee1b1d75b0e6ae8b2bc541c7ed
6
+ metadata.gz: 39a7cc409d442888446e1329d121028e99ef6c71aa09be23dba13d3a5680590896d9d4cacbe7b93e678db4e5b96bbee40cbd37edab21d75e7c6c3694816eb1cd
7
+ data.tar.gz: 0e93cc5d3d37c282a6ed4f50e149d94052299dabe9c7bcea7a8d44e90995ac77f3a2cd60c30360273d274e6b50e012c147c06d2773a382ed87cbc3ab605a1216
data/bin/rsh CHANGED
@@ -8,7 +8,7 @@
8
8
  # Web_site: http://isene.com/
9
9
  # Github: https://github.com/isene/rsh
10
10
  # License: Public domain
11
- @version = "1.2"
11
+ @version = "2.1"
12
12
 
13
13
  # MODULES, CLASSES AND EXTENSIONS
14
14
  class String # Add coloring to strings (with escaping for Readline)
@@ -88,6 +88,12 @@ module Cursor # Terminal cursor movement ANSI codes (thanks to https://github.co
88
88
  print(CSI + 'J')
89
89
  end
90
90
  end
91
+ def stdin_clear
92
+ begin
93
+ $stdin.getc while $stdin.ready?
94
+ rescue
95
+ end
96
+ end
91
97
 
92
98
  # INITIALIZATION
93
99
  begin # Requires
@@ -174,7 +180,7 @@ def firstrun
174
180
  puts "Since there is no rsh configuration file (.rshrc), I will help you set it up to suit your needs.\n\n"
175
181
  puts "The prompt you see now is the very basic rsh prompt:"
176
182
  print "#{@prompt} (press ENTER)"
177
- STDIN.gets
183
+ $stdin.gets
178
184
  puts "\nI will now change the prompt into something more useful."
179
185
  puts "Feel free to amend the prompt in your .rshrc.\n\n"
180
186
  rc = <<~RSHRC
@@ -199,27 +205,28 @@ RSHRC
199
205
  File.write(Dir.home+'/.rshrc', rc)
200
206
  end
201
207
  def getchr # Process key presses
202
- c = STDIN.getch
208
+ c = $stdin.getch
203
209
  case c
204
- when "\e" # ANSI escape sequences
205
- case STDIN.getc
210
+ when "\e" # ANSI escape sequences (with only ESC, it should stop right here)
211
+ return "ESC" if $stdin.ready? == nil
212
+ case $stdin.getc
206
213
  when '[' # CSI
207
- case STDIN.getc
214
+ case $stdin.getc # Will get (or ASK) for more (remaining part of special character)
208
215
  when 'A' then chr = "UP"
209
216
  when 'B' then chr = "DOWN"
210
217
  when 'C' then chr = "RIGHT"
211
218
  when 'D' then chr = "LEFT"
212
219
  when 'Z' then chr = "S-TAB"
213
- when '2' then chr = "INS" ; chr = "C-INS" if STDIN.getc == "^"
214
- when '3' then chr = "DEL" ; chr = "C-DEL" if STDIN.getc == "^"
215
- when '5' then chr = "PgUP" ; chr = "C-PgUP" if STDIN.getc == "^"
216
- when '6' then chr = "PgDOWN" ; chr = "C-PgDOWN" if STDIN.getc == "^"
217
- when '7' then chr = "HOME" ; chr = "C-HOME" if STDIN.getc == "^"
218
- when '8' then chr = "END" ; chr = "C-END" if STDIN.getc == "^"
220
+ when '2' then chr = "INS" ; chr = "C-INS" if $stdin.getc == "^"
221
+ when '3' then chr = "DEL" ; chr = "C-DEL" if $stdin.getc == "^"
222
+ when '5' then chr = "PgUP" ; chr = "C-PgUP" if $stdin.getc == "^"
223
+ when '6' then chr = "PgDOWN" ; chr = "C-PgDOWN" if $stdin.getc == "^"
224
+ when '7' then chr = "HOME" ; chr = "C-HOME" if $stdin.getc == "^"
225
+ when '8' then chr = "END" ; chr = "C-END" if $stdin.getc == "^"
219
226
  else chr = ""
220
227
  end
221
228
  when 'O' # Set Ctrl+ArrowKey equal to ArrowKey; May be used for other purposes in the future
222
- case STDIN.getc
229
+ case $stdin.getc
223
230
  when 'a' then chr = "C-UP"
224
231
  when 'b' then chr = "C-DOWN"
225
232
  when 'c' then chr = "C-RIGHT"
@@ -246,6 +253,7 @@ def getchr # Process key presses
246
253
  when /[[:print:]]/ then chr = c
247
254
  else chr = ""
248
255
  end
256
+ stdin_clear
249
257
  return chr
250
258
  end
251
259
  def getstr # A custom Readline-like function
@@ -260,11 +268,15 @@ def getstr # A custom Readline-like function
260
268
  @c.clear_line
261
269
  print @prompt
262
270
  row, @pos0 = @c.pos
271
+ #@history[0] = "" if @history[0].nil?
263
272
  print cmd_check(@history[0])
264
- @ci = @history[1..].find_index {|e| e =~ /^#{Regexp.escape(@history[0])}./}
265
- @ci += 1 unless @ci == nil
266
- if @history[0].length > 1 and @ci
267
- print @history[@ci][@history[0].length..].to_s.c(@c_stamp)
273
+ @ci = @history[1..].find_index {|e| e =~ /^#{Regexp.escape(@history[0].to_s)}./}
274
+ unless @ci == nil
275
+ @ci += 1
276
+ @ciprompt = @history[@ci][@history[0].to_s.length..].to_s
277
+ end
278
+ if @history[0].to_s.length > 1 and @ci
279
+ print @ciprompt.c(@c_stamp)
268
280
  right = true
269
281
  end
270
282
  @c.col(@pos0 + @pos)
@@ -283,10 +295,9 @@ def getstr # A custom Readline-like function
283
295
  @c.row(1)
284
296
  @c.clear_screen_down
285
297
  when 'UP' # Go up in history
286
- #@history[0] = @history[@history[(@stk+1)..].index{|h| h =~ /#{@history[0]}/}+1] #Future magick
287
298
  if @stk == 0 and @history[0].length > 0
288
299
  @tabsearch = @history[0]
289
- tabbing("hist")
300
+ tab("hist")
290
301
  else
291
302
  if lift
292
303
  @history.unshift("")
@@ -296,6 +307,7 @@ def getstr # A custom Readline-like function
296
307
  unless @stk >= @history.length - 1
297
308
  @stk += 1
298
309
  @history[0] = @history[@stk].dup
310
+ @history[0] = "" unless @history[0]
299
311
  @pos = @history[0].length
300
312
  end
301
313
  lift = false
@@ -368,6 +380,7 @@ def getstr # A custom Readline-like function
368
380
  @pos = 0
369
381
  else
370
382
  @history[0] = @history[@stk].dup
383
+ @history[0] = "" unless @history[0]
371
384
  @pos = @history[0].length
372
385
  end
373
386
  when 'LDEL' # Delete readline (Ctrl-U)
@@ -376,7 +389,8 @@ def getstr # A custom Readline-like function
376
389
  lift = true
377
390
  when 'TAB' # Tab completion of dirs and files
378
391
  @ci = nil
379
- @tabsearch =~ /^-/ ? tabbing("switch") : tabbing("all")
392
+ #@tabsearch =~ /^-/ ? tabbing("switch") : tabbing("all")
393
+ tab("all")
380
394
  lift = true
381
395
  when 'S-TAB'
382
396
  @ci = nil
@@ -387,138 +401,133 @@ def getstr # A custom Readline-like function
387
401
  @pos += 1
388
402
  lift = true
389
403
  end
390
- while STDIN.ready?
391
- chr = STDIN.getc
404
+ while $stdin.ready?
405
+ chr = $stdin.getc
392
406
  @history[0].insert(@pos,chr)
393
407
  @pos += 1
394
408
  end
395
409
  end
410
+ @c.col(@pos0 + @history[0].length)
411
+ @c.clear_screen_down
396
412
  end
397
- def tabbing(type)
398
- @tabstr = @history[@stk][0...@pos]
399
- @tabend = @history[@stk][@pos..]
400
- elements = @tabstr.split(" ")
401
- if @tabstr.match(" $")
402
- elements.append("")
403
- @tabsearch = ""
404
- else
405
- @tabsearch = elements.last.to_s
406
- @tabstr = @tabstr[0...-@tabsearch.length]
407
- end
408
- i = elements.length - 1
409
- if @tabsearch =~ /^-/
410
- until i == 0
411
- i -= 1
412
- if elements[i] !~ /^-/
413
- tab_switch(elements[i])
414
- break
415
- end
416
- end
417
- elsif type == 'all'
418
- tab_all(@tabsearch)
419
- elsif type == 'hist'
420
- tab_hist(@tabsearch)
421
- end
422
- @history[@stk] = @tabstr.to_s + @tabsearch.to_s + @tabend.to_s
423
- end
424
- def tab_all(str) # TAB completion for Dirs/files, nicks and commands
425
- exe = []
426
- @path.each do |p|
427
- Dir.glob(p).each do |c|
428
- exe.append(File.basename(c)) if File.executable?(c) and not Dir.exist?(c)
429
- end
430
- end
431
- exe.prepend(*@nick.keys)
432
- exe.prepend(*@gnick.keys)
433
- compl = exe.select {|s| s =~ Regexp.new("^" + str)}
434
- fdir = str
435
- fdir += "/" if Dir.exist?(fdir)
436
- fdir += "*"
437
- compl.prepend(*Dir.glob(fdir))
438
- match = tabselect(compl) unless compl.empty?
439
- @tabsearch = match if match
440
- @pos = @tabstr.length + @tabsearch.length if match
441
- end
442
- def tab_switch(str) # TAB completion for command switches (TAB after "-")
443
- begin
444
- hlp = `#{str} --help 2>/dev/null`
445
- hlp = hlp.split("\n").grep(/^\s*-{1,2}[^-]/)
446
- hlp = hlp.map{|h| h.sub(/^\s*/, '').sub(/^--/, ' --')}
447
- switch = tabselect(hlp)
448
- switch = switch.sub(/ .*/, '').sub(/,/, '')
449
- @tabsearch = switch if switch
450
- @pos = @tabstr.length + @tabsearch.length
451
- rescue
452
- end
453
- end
454
- def tab_hist(str)
455
- sel = @history.select {|el| el =~ /#{str}/}
456
- sel.shift
457
- sel.delete("")
458
- hist = tabselect(sel, true)
459
- if hist
460
- @tabsearch = hist
461
- @tabstr = ""
462
- @pos = @tabsearch.length
463
- end
464
- end
465
- def tabselect(ary, hist=false) # Let user select from the incoming array
466
- ary.uniq!
467
- @c_row, @c_col = @c.pos
468
- chr = ""
413
+
414
+ def tab(type)
469
415
  i = 0
416
+ chr = ""
417
+ @tabarray = []
418
+ @pretab = @history[0][0...@pos].to_s # Extract the current line up to cursor
419
+ @postab = @history[0][@pos..].to_s # Extract the current line from cursor to end
420
+ @c_row, @c_col = @c.pos # Get cursor position
421
+ @tabstr = @pretab.split(/[|, ]/).last.to_s # Get the sustring that is being tab completed
422
+ @tabstr = "" if @pretab[-1] =~ /[ |]/
423
+ @pretab = @pretab.delete_suffix(@tabstr)
424
+ type = "switch" if @tabstr[0] == "-"
470
425
  while chr != "ENTER"
471
- @c.clear_screen_down
472
- ary.length - i < 5 ? l = ary.length - i : l = 5
473
- l.times do |x|
474
- tl = @tabsearch.length
475
- if x == 0
476
- @c.clear_line
477
- tabchoice = ary[i].sub(/^ *(.*?)[ ,].*/, '\1')
478
- tabline = "#{@prompt}#{cmd_check(@tabstr)}#{tabchoice.c(@c_tabselect)}#{@tabend}"
479
- print tabline # Full command line
480
- @c_col = @pos0 + @tabstr.length + tabchoice.length
481
- nextline
482
- print " " + ary[i].sub(/(.*?)#{@tabsearch}(.*)/, '\1'.c(@c_tabselect) + @tabsearch + '\2'.c(@c_tabselect))
426
+ case type
427
+ when "hist" # Handle history completions ('UP' key)
428
+ @tabarray = @history.select {|el| el =~ /#{@tabstr}/} # Select history items matching @tabstr
429
+ @tabarray.shift # Take away @history[0]
430
+ return if @tabarray.empty?
431
+ when "switch"
432
+ cmdswitch = @pretab.split(/[|, ]/).last.to_s
433
+ hlp = `#{cmdswitch} --help 2>/dev/null`
434
+ hlp = hlp.split("\n").grep(/^\s*-{1,2}[^-]/)
435
+ hlp = hlp.map{|h| h.sub(/^\s*/, '').sub(/^--/, ' --')}
436
+ hlp = hlp.reject{|h| /-</ =~ h}
437
+ @tabarray = hlp
438
+ when "all" # Handle all other tab completions
439
+ exe = []
440
+ @path.each do |p| # Add all executables in @path
441
+ Dir.glob(p).each do |c|
442
+ exe.append(File.basename(c)) if File.executable?(c) and not Dir.exist?(c)
443
+ end
444
+ end
445
+ exe.sort!
446
+ exe.prepend(*@nick.keys) # Add nicks
447
+ exe.prepend(*@gnick.keys) # Add gnicks
448
+ compl = exe.select {|s| s =~ Regexp.new(@tabstr)} # Select only that which matches so far
449
+ fdir = @tabstr + "*"
450
+ compl.prepend(*Dir.glob(fdir)).map! do |e|
451
+ if e =~ /(?<!\\) /
452
+ e = e.sub(/(.*\/|^)(.*)/, '\1\'\2\'') unless e =~ /'/
453
+ end
454
+ Dir.exist?(e) ? e + "/" : e # Add matching dirs
455
+ end
456
+ @tabarray = compl # Finally put it into @tabarray
457
+ end
458
+ return if @tabarray.empty?
459
+ @tabarray.delete("") # Don't remember why
460
+ @c.clear_screen_down # Here we go
461
+ @tabarray.length.to_i - i < 5 ? l = @tabarray.length.to_i - i : l = 5 # Max 5 rows of completion items
462
+ l.times do |x| # Iterate through
463
+ if x == 0 # First item goes onto the commandline
464
+ @c.clear_line # Clear the line
465
+ tabchoice = @tabarray[i] # Select the item from the @tabarray
466
+ tabchoice = tabchoice.sub(/\s*(-.*?)[,\s].*/, '\1') if type == "switch"
467
+ @newhist0 = @pretab + tabchoice + @postab # Remember now the new value to be given to @history[0]
468
+ line1 = cmd_check(@pretab).to_s # Syntax highlight before @tabstr
469
+ line2 = cmd_check(@postab).to_s # Syntax highlight after @tabstr
470
+ # Color and underline the current tabchoice on the commandline:
471
+ tabline = tabchoice.sub(/(.*)#{@tabstr}(.*)/, '\1'.c(@c_tabselect) + @tabstr.u.c(@c_tabselect) + '\2'.c(@c_tabselect))
472
+ print @prompt + line1 + tabline + line2 # Print the commandline
473
+ @pos = @pretab.length.to_i + tabchoice.length.to_i # Set the position on that commandline
474
+ @c_col = @pos0 + @pos # The cursor position must include the prompt as well
475
+ @c.col(@c_col) # Set the cursor position
476
+ nextline # Then start showing the completion items
477
+ tabline = @tabarray[i] # Get the next matching tabline
478
+ # Can't nest ANSI codes, they must each complete/conclude or they will mess eachother up
479
+ tabline1 = tabline.sub(/(.*?)#{@tabstr}.*/, '\1').c(@c_tabselect) # Color the part before the @tabstr
480
+ tabline2 = tabline.sub(/.*?#{@tabstr}(.*)/, '\1').c(@c_tabselect) # Color the part after the @tabstr
481
+ print " " + tabline1 + @tabstr.c(@c_tabselect).u + tabline2 # Color & underline @tabstr
483
482
  else
484
483
  begin
485
- print " " + ary[i+x].sub(/(.*?)#{@tabsearch}(.*)/, '\1'.c(@c_taboption) + @tabsearch + '\2'.c(@c_taboption))
484
+ tabline = @tabarray[i+x] # Next tabline, and next, etc (usually 4 times here)
485
+ tabline1 = tabline.sub(/(.*?)#{@tabstr}.*/, '\1').c(@c_taboption) # Color before @tabstr
486
+ tabline2 = tabline.sub(/.*?#{@tabstr}(.*)/, '\1').c(@c_taboption) # Color after @tabstr
487
+ print " " + tabline1 + @tabstr.c(@c_taboption).u + tabline2 # Print the whole line
486
488
  rescue
487
489
  end
488
490
  end
489
- nextline
491
+ nextline # To not run off screen
490
492
  end
491
- @c.row(@c_row)
492
- @c.col(@c_col)
493
- chr = getchr
494
- case chr
495
- when 'C-G', 'C-C'
496
- @c.clear_screen_down
497
- return @tabsearch
493
+ @c.row(@c_row) # Set cursor row to commandline
494
+ @c.col(@c_col) # Set cursor col on commandline
495
+ chr = getchr # Now get user input
496
+ case chr # Treat the keypress
497
+ when 'C-G', 'C-C', 'ESC'
498
+ tabend; return
498
499
  when 'DOWN'
499
- i += 1 unless i > ary.length - 2
500
+ i += 1 unless i > @tabarray.length.to_i - 2
500
501
  when 'UP'
501
502
  i -= 1 unless i == 0
502
- when 'TAB'
503
+ when 'TAB', 'RIGHT' # Effectively the same as ENTER
503
504
  chr = "ENTER"
504
- when 'BACK'
505
- @tabsearch.chop!
506
- if @tabsearch == ''
507
- @c.clear_screen_down
508
- return ""
505
+ when 'BACK', 'LEFT' # Delete one character to the left
506
+ if @tabstr == ""
507
+ @history[0] = @pretab + @postab
508
+ tabend
509
+ return
510
+ end
511
+ @tabstr.chop!
512
+ when 'WBACK' # Delete one word to the left (Ctrl-W)
513
+ if @tabstr == ""
514
+ @history[0] = @pretab + @postab
515
+ tabend
516
+ return
509
517
  end
510
- hist ? tab_hist(@tabsearch) : tab_all(@tabsearch)
511
- return @tabsearch
518
+ @tabstr.sub!(/#{@tabstr.split(/[|, ]/).last}.*/, '')
519
+ when ' '
520
+ @tabstr += " "
521
+ chr = "ENTER"
512
522
  when /^[[:print:]]$/
513
- @tabsearch += chr
514
- hist ? tab_hist(@tabsearch) : tab_all(@tabsearch)
515
- return @tabsearch
523
+ @tabstr += chr
524
+ i = 0
516
525
  end
517
526
  end
518
527
  @c.clear_screen_down
519
528
  @c.row(@c_row)
520
529
  @c.col(@c_col)
521
- return ary[i].sub(/^ */, '')
530
+ @history[0] = @newhist0
522
531
  end
523
532
  def nextline # Handle going to the next line in the terminal
524
533
  row, col = @c.pos
@@ -528,26 +537,33 @@ def nextline # Handle going to the next line in the terminal
528
537
  end
529
538
  @c.next_line
530
539
  end
540
+ def tabend
541
+ @c.clear_screen_down
542
+ @pos = @history[0].length
543
+ @c_col = @pos0 + @pos
544
+ @c.col(@c_col)
545
+ end
531
546
  def hist_clean # Clean up @history
532
547
  @history.uniq!
533
548
  @history.compact!
534
549
  @history.delete("")
535
550
  end
536
551
  def cmd_check(str) # Check if each element on the readline matches commands, nicks, paths; color them
537
- str.gsub(/\S+/) do |el|
552
+ return if str.nil?
553
+ str.gsub(/(?:\S'[^']*'|[^ '])+/) do |el|
538
554
  if @nick.include?(el)
539
555
  el.c(@c_nick)
540
- elsif el == "r"
556
+ elsif el == "r" or el == "f"
541
557
  el.c(@c_nick)
542
558
  elsif @gnick.include?(el)
543
559
  el.c(@c_gnick)
544
- elsif File.exist?(el.sub(/^~/, "/home/#{@user}"))
560
+ elsif File.exist?(el.gsub("'", ""))
545
561
  el.c(@c_path)
546
562
  elsif system "which #{el}", %i[out err] => File::NULL
547
563
  el.c(@c_cmd)
548
564
  elsif el == "cd"
549
565
  el.c(@c_cmd)
550
- elsif el =~ /^-/
566
+ elsif el[0] == "-"
551
567
  el.c(@c_switch)
552
568
  else
553
569
  el
@@ -568,7 +584,7 @@ def rshrc # Write updates to .rshrc
568
584
  conf.sub!(/^@history.*\n/, "")
569
585
  conf += "@history = #{@history.last(@histsize)}\n"
570
586
  File.write(Dir.home+'/.rshrc', conf)
571
- puts ".rshrc updated"
587
+ puts "\n.rshrc updated"
572
588
  end
573
589
 
574
590
  # RSH FUNCTIONS
@@ -654,6 +670,7 @@ loop do
654
670
  h = @history; load(Dir.home+'/.rshrc') if File.exist?(Dir.home+'/.rshrc'); @history = h # reload prompt but not history
655
671
  @prompt.gsub!(/#{Dir.home}/, '~') # Simplify path in prompt
656
672
  system("printf \"\033]0;rsh: #{Dir.pwd}\007\"") # Set Window title to path
673
+ @history[0] = "" unless @history[0]
657
674
  getstr # Main work is here
658
675
  @cmd = @history[0]
659
676
  @dirs.unshift(Dir.pwd)
@@ -721,7 +738,7 @@ loop do
721
738
  else
722
739
  begin
723
740
  pre_cmd
724
- puts "Not executed: #{@cmd}" unless system (@cmd) # Try execute the command
741
+ puts " Not executed: #{@cmd}" unless system (@cmd) # Try execute the command
725
742
  post_cmd
726
743
  rescue StandardError => err
727
744
  puts "\n#{err}"
metadata CHANGED
@@ -1,18 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-shell
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.2'
4
+ version: '2.1'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-10 00:00:00.000000000 Z
11
+ date: 2024-10-23 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: 'A shell written in Ruby with extensive tab completions, aliases/nicks,
14
14
  history, syntax highlighting, theming, auto-cd, auto-opening files and more. In
15
- continual development. New in 1.2: Fixed bug on tab handling'
15
+ continual development. New in 2.0: Full rewrite of tab completion engine. Lots of
16
+ other bug fixes. 2.1: Better error handling.'
16
17
  email: g@isene.com
17
18
  executables:
18
19
  - rsh