#!/usr/bin/env ruby # diru is a tool for moving around in the users directory # structure. diru provides input for the shell cd command for entering # a new directory. However some commands do not produce input for cd. # # diru has hub (daemon) which creates new service (server) threads for # diru clients. Before user can start using diru, a server should be # created. # # diru has server side and client side. The server maintains # information about users directories and also the state of the # queries issued by the client. Client is what the end user uses. # # diru client command has to be integrated to the currently used # shell in order to force the shell to change the current # directory. Lets assume that the shell alias/function that user is # actually using is "dr" for now. If diru returns success, then the # diru output should be used as new directory. If diru status is # failure, then directory should not be changed. # # Example of diru use: # shell> dr / build src # # This command means that the client sends to the server a request for # directory search. The search starts from Diru root ("/") and the # rest of the path should include the words "build", and # "src". If path with this spec is found, the shell directory gets # updated. # # Perform: # shell> dr i # # for some documentation. # # Diru setup: # # Start hub (deamon) # * shell> diru --hub # # Open server for client. # * shell> diru -s # # Use client # * shell> diru -p 41115 -c r # # # Feature list: # * User root dir (downwards) search # * Current directory (downwards) dir search # * Bookmarking directories # * Left-over search with multiple matches # * Fast jump to specific common directories # * Peer dir jump # * Server state reset # * Revert mode, unknown commands to cd # require 'como' include Como require 'drb' require 'json' require 'yaml' require 'fileutils' # require 'byebug' Spec.command( 'diru', 'Tero Isannainen', '2017', [ [ :switch, 'hub', nil, "H: Start hub." ], [ :opt_single, 'hport', nil, "H: Hub port (default: DIRU_HUB_PORT or 41114)." ], [ :switch, 'hkill', nil, "H: Kill hub." ], [ :switch, 'nodaemon', nil, "H: No daemon for server." ], [ :switch, 'start', nil, "HS: Start hub and default server." ], [ :opt_any, 'server', '-s', "S: Open server for user (default: $USER)." ], [ :opt_single, 'kill', '-k', "S: Close server." ], [ :switch, 'list', '-l', "S: List servers." ], [ :opt_single, 'options', '-o', "SC: Options File." ], [ :opt_single, 'port', '-p', "SC: Server port for client (default: DIRU_PORT or ~/.diru.prt)." ], [ :switch, 'change', '-c', "C: Change dir (i.e. target is to change dir)." ], [ :exclusive, 'template', '-t', "C: Display Options File template." ], [ :exclusive, 'cmddoc', '-d', "C: Display command documentation." ], [ :default, nil, nil, "C: Client commands (try: diru i)." ], ] ) # ------------------------------------------------------------ # Common: # ------------------------------------------------------------ # Diru common features. module Diru # Default port. DIRU_HUB_PORT = ENV['DIRU_HUB_PORT'] || 41114 # See: Diru.load_conf def load_conf( conf_file ) @conf = conf_file.load end # Error message display. def Diru.error_msg( msg ) STDERR.puts "Diru Error: #{msg}" end # Error message display with exit. def Diru.error( msg ) Diru.error_msg( msg ) exit( false ) end # Warning message display. def Diru.warn( msg ) STDERR.puts "Diru Warning: #{msg}" end # List the dir content (default: pwd). def Diru.list( dir = '.', glob = '*' ) Dir.glob( "#{dir}/#{glob}" ) end # List the dir files (default: pwd). def Diru.list_files( dir = '.', glob = '*' ) Diru.list( dir, glob ).select do |i| File.file?(i) == true end end # Find file entry from directory hierarchy upwards. def Diru.find_upper_file( file, dir = Dir.pwd ) found = Diru.list_files( dir, file ) if found.empty? dir = File.dirname( dir ) if dir == "/" raise RuntimeError, "Could not find file \"#{file}\"!" else return Diru.find_upper_file( file, dir ) end else return found[0] end end # Find file entry from directory hierarchy upwards. def Diru.find_an_upper_file( files, dir = Dir.pwd ) found = nil # Process all directory levels. while true # Process directory level. while true # Process all files per directory level. idx = 0 files.each do |file| match = Dir.glob( "#{dir}/#{file}" ) unless match.empty? found = match[0] break end end break if found end break if found if dir == "/" raise RuntimeError, "Could not find file(s): \"#{files.join("\", \"")}\"!" end dir = File.dirname( dir ) end found end end # Diru configuration file. class DiruConf # Convert YAML config to hash with String keys at highest level. def DiruConf.stringify_yaml_keys( entry ) ret = {} entry.each do |k,v| if k.is_a? String key = k else key = k.to_s end ret[ key ] = v end ret end def initialize( filename ) @filename = filename end end # Diru configuration file in JSON. class DiruConfJson < DiruConf def load if File.exist?( @filename ) JSON.parse( File.read( @filename ) ) else STDERR.puts "Diru Error: Broken JSON, please fix: \"#{@filename}\"..." nil end end end # Diru configuration file in YAML. class DiruConfYaml < DiruConf def load if File.exist?( @filename ) DiruConf.stringify_yaml_keys( YAML.load( File.read( @filename ) ) ) else STDERR.puts "Diru Error: Broken YAML, please fix: \"#{@filename}\"..." nil end end end # Diru Server State. class Search include Diru def initialize( root, user, opts_file = nil ) # Server root. @root = root if @root == '/' @rootpart = '' else @rootpart = @root end # Change directory to DIRU root. Dir.chdir( @root ) # User of server. @user = user # List of old matches. @old = [] # Numbered bookmarks. @book = [] # Numbered history. @hist = [] # Favorites. @fav = {} # Lookup database. @lut = {} # History limit. @histlimit = 20 # Sync period for options (and data if no @dsync). @sync = 10 # Sync period for data. @dsync = nil # Options File. @opts_file = opts_file # Temporary storage (scratch pad). @scratch = {} # Glob pattern for DB build. @glob = "**/*" @logging = false # if Opt['log'].given # @logging = true # end # Data update thread. @th_data = nil # Options update thread. @th_opts = nil # Lock for DB access. @datalock = Mutex.new @optslock = Mutex.new # Initial data update. update_conf update_data start_th_data start_th_opts end # Return opts_file. def opts_file @opts_file end # Return root. def root @root end # Return user. def user @user end # Add bookmark (if not already). def abook( dir ) idx = @book.index dir unless idx @book.unshift dir end end # Delete bookmark. def dbook( idx ) @book.delete_at idx end # Get bookmark. def gbook( idx ) @book[ idx ] end # Get all bookmarks. def book @book end # Reset bookmarks. def rbook @book = [] end # Save bookmarks to file. def savebook( file ) File.write( file, @book.join("\n") + "\n" ) end # Load bookmarks from file. def loadbook( file ) @book = File.read( file ).split("\n") end # Add history. def ahist( dir ) if dir != @hist[0] if @hist.length >= @histlimit @hist = [ dir ] + @hist[ 0..(@histlimit-2) ] else @hist = [ dir ] + @hist end end end # Get history. def ghist( idx ) @hist[ idx ] end # Get all historys. def hist @hist end # Reset history. def rhist @hist = [] end # Get (and update) favorite. def gfav( tag ) "#{@rootpart}/#{@fav[ tag ]}" end # Return the whole fav. def fav @fav end # Update options. def update_conf if @opts_file return unless load_conf( @opts_file ) if @conf[ "dsync" ] && @conf[ "dsync" ] >= 0 @dsync = @conf[ "dsync" ] end if @conf[ "sync" ] && @conf[ "sync" ] >= 1 @sync = @conf[ "sync" ] @dsync = @sync unless @dsync end if @dsync == 0 kill_th_data else unless @th_data start_th_data end end if @conf[ "hist" ] && ( @conf[ "hist" ] >= 2 ) @histlimit = @conf[ "hist" ] else @histlimit = 20 end if @conf[ "favs" ] @fav = @conf[ "favs" ] end end end # Set scratch dir. def settmp( dir, reg = nil ) @scratch[ reg ] = dir end # Get scratch dir. def gettmp( reg = nil ) @scratch[ reg ] end # Reset scratch dir. def rtmp @scratch = {} end # Get all scratch regs. def scratch @scratch end # Return LUT, key/value store entries. def lut @lut end # Reset LUT entry or all entries. def rlut( key = nil ) if key @lut.delete key else @lut = {} end end # Get value from LUT. def getlut( key ) @lut[ key ] end # Set value in LUT. def setlut( key, value ) @lut[ key ] = value end def abs2rel( path ) if path == @root '' else rem = "#{@rootpart}/" rel = path.dup rel[rem] = '' rel end end # Match dir/pattern with glob (fnmatch) from DB. def match( dir, pattern ) list = [] if dir begin dir = abs2rel( dir ) rescue return [] end r = dir + '*' + pattern else r = pattern end @datalock.synchronize do @data.each do |i| if File.fnmatch( r, i ) list.push "#{@rootpart}/#{i}" end end end # Update old list. @old = list.rotate list end # Match dir/pattern with regex from DB. def rematch( dir, pattern ) list = [] if dir begin dir = abs2rel( dir ) rescue return [] end r = dir + '.*' + pattern else r = pattern end @datalock.synchronize do @data.each do |i| if i.match( r ) list.push "#{@rootpart}/#{i}" end end end # Update old list. @old = list.rotate list end # Get next match from old. def next ret = @old[0] @old.rotate! ret end # Make directory. def mk_dir( dir ) rel = abs2rel( dir ) unless @data.index( rel ) @data = @data.push( rel ).sort FileUtils.mkdir_p dir end end # Make directory in sync. def mk_dir_sync( dir ) @datalock.synchronize do mk_dir dir end end # Remove directory. def rm_dir( dir ) rel = abs2rel( dir ) if @data.index( rel ) @data.delete( rel ) FileUtils.rmdir dir end end # Remove directory in sync. def rm_dir_sync( dir ) @datalock.synchronize do rm_dir dir end end # Update directory DB. # # Relative dir data under Project root. # test # test/dir_0 # ... def update_data @data = Dir.glob( @glob ).select{|ent| File.directory?( ent )} @data = @data.sort end # Update DB with MT sync. def update_data_sync @datalock.synchronize do update_data end end # Update DB with MT sync. def update_opts_sync @optslock.synchronize do update_conf end end # Kill search and cleanup resources. def kill kill_th_data kill_th_opts end # Reset dynamic state. def reset log "reset" @old = [] @book = [] @hist = [] @lut = {} update_data_sync end # Start data thread. def start_th_data unless @dsync == 0 @th_data = Thread.new do sleep 1 loop do # puts "data update..." update_data_sync sleep @dsync end end end end # Kill data thread. def kill_th_data Thread.kill( @th_data ) if @th_data @th_data = nil end # Start opts thread. def start_th_opts @th_opts = Thread.new do sleep 1 loop do update_opts_sync sleep @sync end end end # Kill opts thread. def kill_th_opts Thread.kill( @th_opts ) if @th_opts @th_opts = nil end # Enable logging. def logon @logging = true end # Disable logging. def logoff @logging = false end # Log events. def log( msg ) if @logging fh = File.open( "#{ENV['HOME']}/.diru.log", "a" ) fh.puts "#{Time.timestamp()}: #{msg}" fh.close end end end # ------------------------------------------------------------ # Server program. # ------------------------------------------------------------ # Diru hub. class Hub # Start in daemon or direct mode. def Hub.start( port, daemon = true ) if daemon p = fork do begin Hub.new( port ) rescue Diru.error "Could not start Hub!" exit( false ) end end Process.detach( p ) else begin Hub.new( port ) rescue Diru.error "Could not start Hub!" exit( false ) end end end # Server collection. @@servers = {} attr_reader :port def initialize( port ) @port = port # Start the service @hub = DRb.start_service( "druby://localhost:#{@port}", self ) DRb.thread.join end # Kill hub. def kill @th = Thread.new do STDERR.puts "Diru Hub: Killing servers ..." kill_servers STDERR.puts "Diru Hub: Exit done ..." @hub.stop_service exit( false ) end end # Create new server and return port for the server. def get_server( root, user, opts_file ) 50.times do |i| if @@servers[ @port+1+i ] == nil port = @port+1+i s = Service.new( root, user, port, opts_file ) @@servers[ port ] = s return port end end 0 end # List all server ports. def list_servers @@servers.keys.map{ |i| [ i, @@servers[i].root, @@servers[i].user ] } end # Kill all servers. def kill_servers @@servers.keys.each do |s| kill_server( s ) end @@servers = {} end # Kill server with the given port. def kill_server( s_port ) s = @@servers[ s_port ] if s alive = s.alive? cnt = 0 while alive && cnt < 20 s.kill sleep 0.1 alive = s.alive? cnt += 1 end @@servers.delete( s_port ) else nil end end end # Diru client service (server) thread. class Service def Service.start( hport, user, try_cnt = 3 ) # Setup client server. hub = DRbObject.new( nil, "druby://localhost:#{hport}" ) root = nil # Define project root, if it is explicitly defined. if ENV['DIRU_ROOT'] root = ENV['DIRU_ROOT'] else # First search for .diru_root_dir file. begin root = File.dirname( Diru.find_upper_file( '.diru_root_dir' ) ) rescue root = nil end end opts_json = ".diru.json" opts_yaml = ".diru.yml" opts_filename = nil opts_file = nil if root if Opt['options'].given opts_filename = Opt['options'].value elsif ENV['DIRU_OPTS'] opts_filename = ENV['DIRU_OPTS'] elsif File.exists? "#{ENV['HOME']}/#{opts_json}" opts_filename = "#{ENV['HOME']}/#{opts_json}" elsif File.exists? "#{ENV['HOME']}/#{opts_yaml}" opts_filename = "#{ENV['HOME']}/#{opts_yaml}" else opts_filename = nil end else # No explicit root, hence derive from options file (if any). begin file = Diru.find_an_upper_file( [ opts_json, opts_yaml ] ) root = File.dirname( file ) opts_filename = file rescue Diru.error "Could not find user directory root!" end end if opts_filename case File.extname( opts_filename ) when ".json"; opts_file = DiruConfJson.new( opts_filename ) when ".yml"; opts_file = DiruConfYaml.new( opts_filename ) else raise RuntimeError, "Wrong Diru configuration file format (i.e. not json or yaml)!" end else raise RuntimeError, "Missing configuration file!" end s_port = 0 cnt = 0 while ( s_port == 0 && cnt < try_cnt ) begin s_port = hub.get_server( root, user, opts_file ) rescue Diru.warn "Access to Hub failed!" if cnt >= 1 sleep( 1 ) end cnt += 1 end if s_port == 0 Diru.error "Could not start server!" else puts "Using server port: #{s_port}..." end end attr_reader :root attr_reader :user # Initialize and start service. def initialize( root, user, port, opts_file ) @root = root @user = user @port = port @drb = nil @opts_file = opts_file start self end # Response true (if alive). def alive? true end # Kill service. def kill @search.kill Thread.kill( @th ) @drb.stop_service end # Start service. def start @th = Thread.new do # Create "front" object. @search = Search.new( @root, @user, @opts_file ) # Start the service @drb = DRb::DRbServer.new( "druby://localhost:#{@port}", @search ) @drb.thread.join end end end # ------------------------------------------------------------ # Client program. # ------------------------------------------------------------ class Client include Diru attr_reader :pwd attr_accessor :enable_out def initialize( port ) @port = port # Create a DRbObject instance that is connected to server. All methods # executed on this object will be executed to the remote one. @search = DRbObject.new( nil, "druby://localhost:#{@port}" ) raise unless load_conf( @search.opts_file ) @pwd = Dir.pwd @enable_out = true end # Perform cd (i.e. return true) and store current dir to history. def do_cd( dir ) # Store current to history before change. @search.ahist( @pwd ) # Local copy of new dir for command-sequence mode. @pwd = dir # Directory info for shell, unless disabled for # command-sequence mode. puts dir if @enable_out true end # No directory change, i.e. return false. def no_cd false end # Process command. def command( input ) if input.empty? # Refer to last search list. do_cd @search.next else cmd = input[0] arg = input[1..-1] # Shell special chars to avoid: # * ? [ ] ' " \ $ ; & ( ) | ^ < > # Thus allowed: # ! % + , - . / : = @ _ ~ # ^ ^ ^ ^ ^ ^ ^ ^ ^ # Decode user command. ret = \ case cmd when 'r'; if arg.empty? disp @search.root else disp @search.match( nil, "*"+arg.join('*')+"*" ) end when 't'; disp @search.match( @pwd, "*"+arg.join('*')+"*" ) when 'e'; disp @search.rematch( nil, arg.join('.*') ) when 'q'; disp @search.rematch( nil, arg.join('.*') ) when 'b'; if arg[0] == nil lbook( @search.book ) no_cd elsif arg[0] == '.' @search.abook( @pwd ) no_cd elsif arg[0] == '!' @search.rbook no_cd elsif arg[0] == 's' @search.savebook( arg[1] ) no_cd elsif arg[0] == 'l' @search.loadbook( arg[1] ) no_cd elsif arg[0] == 'd' @search.dbook( arg[1].to_i ) no_cd else disp @search.gbook( arg[0].to_i ) end when 'h'; if arg[0] == nil lbook( @search.hist ) no_cd elsif arg[0] == '.' @search.ahist( @pwd ) no_cd elsif arg[0] == '!' @search.rhist no_cd elsif arg[0] == ',' disp @search.ghist( 0 ) else disp @search.ghist( arg[0].to_i ) end when 's'; if arg[0] == nil # Change: "dr s" disp @search.gettmp else if arg[0] == '.' # Store: "dr s ." # OR: "dr s . " reg = nil dir = @pwd if arg[1] && arg[1].length == 1 reg = arg[1] end disp @search.settmp( dir, reg ) no_cd elsif arg[0] == '=' lregs( @search.scratch ) no_cd elsif arg[0] == '!' @search.rtmp no_cd elsif arg[0].length == 1 # Change: "dr s " disp @search.gettmp( arg[0] ) else # Store: "dr s " # OR: "dr s " reg = nil reg = arg[1] if arg[1] disp @search.settmp( arg[0], reg ) no_cd end end when 'p'; disp peer when 'c'; rpc( arg ) when 'f'; if arg[0] == nil @search.fav.each do |k,v| STDERR.puts format( " %-6s %s", k, v ) end no_cd else fav_map( arg ) end when 'l'; if arg[0] == nil @search.lut.each do |k,v| STDERR.puts " #{k.to_s.ljust(16)} #{v}" end else if arg[0] == '!' if arg.length == 1 @search.rlut else @search.rlut( arg[1] ) end else if arg.length == 1 val = @search.getlut( arg[0] ) STDERR.puts val if val else @search.setlut( arg[0], arg[1] ) end end end no_cd when 'd'; # Mkdir: # dr d m # Rmdir: # dr d r if arg.length == 2 if arg[0] == 'm' abs = File.absolute_path( arg[1] ) if abs.match( @search.root ) # New subdir to Project. @search.mk_dir_sync( abs ) else FileUtils.mkdir_p dir end elsif arg[0] == 'r' abs = File.absolute_path( arg[1] ) if abs.match( @search.root ) # New subdir to Project. @search.rm_dir_sync( abs ) else FileUtils.rmdir dir end end end no_cd when 'i'; rpc( [ 'doc' ] ) else if Opt['change'].given all = [cmd] + arg do_cd "#{all.join(' ')}" else no_cd end end # exit( ret ) return ret end end # Display directories. One to STDOUT and others to STDERR. def disp( resp ) if resp == nil no_cd elsif resp.kind_of? Array if resp.any? if resp.length > 1 resp[1..-1].each do |i| STDERR.puts i end end do_cd resp[0] else Diru.warn "Dir not found!" no_cd end else do_cd resp end end # Display directories without CD. def show( resp ) if resp == nil # NOP elsif resp.kind_of? Array if resp.any? resp.each do |i| STDERR.puts i end end else STDERR.puts resp end no_cd end # Get peer directory. Search list of re-pairs. Match either of the # pair, and switch to the pair dir. # def peer # cur = Dir.pwd cur = @pwd peers = @conf[ "peers" ] peers.each do |pair| re = Regexp.new( pair[0] ) if ( re.match( cur ) ) return cur.sub( re, pair[1] ) end end nil end # Display short command doc. def doc STDERR.puts " r - search from root (or to root) t - search from this (current) e - search from root (with regexp) q - query from root (with regexp) b - bookmark access h - history access s - scratch pad access p - peer of current c - command (reset, doc etc.) f - favorites l - lookup key/value store d - directory mutation i - short info " end # List bookmarks with index number, in reverse order. def lbook( book ) idx = book.length-1 (0..idx).to_a.reverse.each do |i| STDERR.puts format( "%2d: %s", i, book[i] ) end end # List registers (key,value). def lregs( regs ) regs.each do |k,v| STDERR.puts format( " %-2s: %s", k, v ) end end # Remove Procedure Call towards server. # # Example: # shell> dr @ rbook # def rpc( arg ) case arg[0] when 'reset'; @search.reset when 'abook'; @search.abook( @pwd ) when 'lbook'; lbook( @search.book ) when 'rbook'; @search.rbook when 'doc'; doc when 'dsync'; @search.update_data_sync when 'sync'; @search.update_opts_sync when 'lregs'; @search.update_data_sync else return no_cd end no_cd end # Direct directory jump. def fav_map( arg ) ret = @search.gfav( arg[0] ) if ret disp ret else no_cd end end end # ------------------------------------------------------------ # Server (and generic) actions. # ------------------------------------------------------------ if Opt['template'].given puts %q{{ "sync": 10, "dsync": 0, "hist": 20, "favs": { "f": "dir_0/dir_0_4/dir_0_4_0", "g": "dir_1/dir_1_2" }, "peers": [ [ "(.*/dir_0/.*)/dir_0_2_0", "\\\\1" ], [ "(.*/dir_0/.*)/dir_0_1_0", "\\\\1/dir_0_1_1" ] ] } } exit( false ) end # Keep in sync with README. if Opt['cmddoc'].given STDERR.puts %q{ Search commands: dr r - change to Project root dir. dr r - change to (somewhere) under Project root dir (glob). dr t - change to (somewhere) under current dir (glob). dr e - change to (somewhere) under Project root dir (regexp). Bookmark commands: dr b - display bookmarks. dr b . - add current dir to bookmarks. dr b ! - reset (clear) bookmarks. dr b s - store bookmarks to . dr b l - load bookmarks from . dr b d - delete bookmark with . dr b - change dir to bookmark . History commands: dr h - display history. dr h . - add current dir to history. dr h ! - reset (clear) history. dr h 0 - reference latest history item. dr h - change dir to history . Scratch Pad commands: dr s . - store current dir to Default Scratch Pad. dr s . - store current dir to in Scratch Pad. dr s - store to Default Scratch Pad. dr s - store to in Scratch Pad. dr s - change dir to Default Scratch Pad. dr s - change dir to from Scratch Pad. dr s = - display Scratch Pad content. dr s ! - reset (clear) Scratch Pad. Lookup commands: dr l - list all lookup / pairs. dr ! - remove all keys. dr ! - remove . dr l - lookup value for . dr l - store value for . Misc commands: dr p - jump to peer dir, i.e. the peer of current (from options). dr c - issue RPC command to server (reset etc., see below). dr f - list favorites dirs (from options). dr f - change dir to favorite . dr d m - make directory. dr d r - remove directory. dr i - show command info. dr - change to given dir (must be under current). dr - change to next "Left-over" directory. } exit( false ) end hport = nil if Opt['hport'].given hport = Opt['hport'].value.to_i else hport = Diru::DIRU_HUB_PORT end if Opt['hkill'].given begin hub = DRbObject.new( nil, "druby://localhost:#{hport}" ) hub.kill rescue Diru.error "Could not kill Hub!" end exit( true ) end if Opt['kill'].given hub = DRbObject.new( nil, "druby://localhost:#{hport}" ) port = Opt['kill'].value.to_i begin hub.kill_server port rescue Diru.error "Access to Hub failed!" end exit( true ) end if Opt['list'].given hub = DRbObject.new( nil, "druby://localhost:#{hport}" ) begin hub.list_servers.each do |i| puts format( "%-12.d %-12s %s", i[0], i[2], i[1] ) end rescue Diru.error "Access to Hub failed!" end exit( true ) end if false # For hub maintenance (irb): require 'drb' hub = DRbObject.new( nil, "druby://localhost:41114" ) hub.list_servers end if false # Test client: require 'drb' @port = 41115 @search = DRbObject.new( nil, "druby://localhost:#{@port}" ) end if Opt['hub'].given # Start Hub. Hub.start( hport, !Opt['nodaemon'].given ) exit( true ) end if Opt['server'].given # Server options. if Opt['server'].value.any? user = Opt['server'].value[0] else user = ENV['USER'] end # Start service. Service.start( hport, user ) exit( true ) end if Opt['start'].given # Start Hub. Hub.start( hport, !Opt['nodaemon'].given ) # Server options. if Opt['server'].value.any? user = Opt['server'].value[0] else user = ENV['USER'] end # Start server. Service.start( hport, user ) exit( true ) end # ------------------------------------------------------------ # Client actions. # ------------------------------------------------------------ if Opt['port'].given port = Opt['port'].value.to_i elsif ENV['DIRU_PORT'] port = ENV['DIRU_PORT'].to_i elsif File.exist?( "#{ENV['HOME']}/.diru.prt" ) port = File.read( "#{ENV['HOME']}/.diru.prt" ).to_i else Diru.error "Server port info missing..." end # User command content. input = Opt[nil].value begin client = Client.new( port ) rescue Diru.error "Server not available!" exit( false ) end # Collect command-sequence (if any). cmds = [] si = 0 ei = 0 while ( ei = input.index "," ) cmds.push input[si...ei] si = ei+1 input = input[si..-1] end # Add the tail command, i.e. the only if no "+" used. cmds.push input ret = false # Disable shell output for all dir changes except last. if cmds.length > 1 client.enable_out = false end cmds.each_with_index do |cmd, i| if i == cmds.length-1 # Enable output for shell for the last dir change. client.enable_out = true end begin ret = client.command( cmd ) rescue Diru.error "Command failure!" exit( false ) end end exit( ret )