Class: Lich::Common::Script

Inherits:
Object
  • Object
show all
Defined in:
lib/common/script.rb

Overview

This class manages the execution of scripts.

Direct Known Subclasses

ExecScript, WizardScript

Constant Summary collapse

@@elevated_script_start =

Starts an elevated script with the given arguments.

Examples:

Script.start(['my_script', { args: 'arg1 arg2' }])

Returns:

  • (nil)

    Returns nil if the script cannot be started.

Raises:

  • (StandardError)

    Raises an error if the script cannot be found or started.

proc { |args|
  if args.empty?
    # fixme: error
    next nil
  elsif args[0].class == String
    script_name = args[0]
    if args[1]
      if args[1].class == String
        script_args = args[1]
        if args[2]
          if args[2].class == Hash
            options = args[2]
          else
            # fixme: error
            next nil
          end
        end
      elsif args[1].class == Hash
        options = args[1]
        script_args = (options[:args] || String.new)
      else
        # fixme: error
        next nil
      end
    else
      options = Hash.new
    end
  elsif args[0].class == Hash
    options = args[0]
    if options[:name]
      script_name = options[:name]
    else
      # fixme: error
      next nil
    end
    script_args = (options[:args] || String.new)
  end
    # fixme: look in wizard script directory
  # fixme: allow subdirectories?
  file_list = Dir.children(File.join(SCRIPT_DIR, "custom")).sort_by { |fn| fn.sub(/[.](lic|rb|cmd|wiz)$/, '') }.map { |s| s.prepend("/custom/") } + Dir.children(SCRIPT_DIR).sort_by { |fn| fn.sub(/[.](lic|rb|cmd|wiz)$/, '') }
  if (file_name = (file_list.find { |val| val =~ /^(?:\/custom\/)?#{Regexp.escape(script_name)}\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/ || val =~ /^(?:\/custom\/)?#{Regexp.escape(script_name)}\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/i } || file_list.find { |val| val =~ /^(?:\/custom\/)?#{Regexp.escape(script_name)}[^.]+\.(?i:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/ } || file_list.find { |val| val =~ /^(?:\/custom\/)?#{Regexp.escape(script_name)}[^.]+\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/i }))
    script_name = file_name.sub(/\..{1,3}$/, '')
  end
  if file_name.nil?
    respond "--- Lich: could not find script '#{script_name}' in directory #{SCRIPT_DIR} or #{SCRIPT_DIR}/custom"
    next nil
  end
  if (options[:force] != true) and (Script.running + Script.hidden).find { |s| s.name =~ /^#{Regexp.escape(script_name.sub('/custom/', ''))}$/i }
    respond "--- Lich: #{script_name} is already running (use #{$clean_lich_char}force [scriptname] if desired)."
    next nil
  end
  begin
    if file_name =~ /\.(?:cmd|wiz)(?:\.gz)?$/i
      trusted = false
      script_obj = WizardScript.new("#{SCRIPT_DIR}/#{file_name}", script_args)
    else
      if script_obj.labels.length > 1
        trusted = false
      else
        trusted = true
      end
      script_obj = Script.new(:file => "#{SCRIPT_DIR}/#{file_name}", :args => script_args, :quiet => options[:quiet])
    end
    if trusted
      script_binding = TRUSTED_SCRIPT_BINDING.call
    else
      script_binding = Scripting.new.script
    end
  rescue
    respond "--- Lich: error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
    next nil
  end
  unless script_obj
    respond "--- Lich: error: failed to start script (#{script_name})"
    next nil
  end
  script_obj.quiet = true if options[:quiet]
  new_thread = Thread.new {
    100.times { break if Script.current == script_obj; sleep 0.01 }
      if (script = Script.current)
      eval('script = Script.current', script_binding, script.name)
      Thread.current.priority = 1
      respond("--- Lich: #{script.name} active.") unless script.quiet
      if trusted
        begin
          eval(script.labels[script.current_label].to_s, script_binding, script.name)
        rescue SystemExit
          nil
        rescue SyntaxError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue ScriptError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue NoMemoryError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue LoadError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue SecurityError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue ThreadError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue SystemStackError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue StandardError # Exception
          if $! == JUMP
            retry if Script.current.get_next_label != JUMP_ERROR
            respond "--- label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!"
            respond $!.backtrace.first
            Lich.log "label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!\n\t#{$!.backtrace.join("\n\t")}"
            Script.current.kill
          else
            respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
            Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
          end
        rescue
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        ensure
          Script.current.kill
        end
      else
        begin
          while (script = Script.current) and script.current_label
            proc { foo = script.labels[script.current_label]; eval(foo, script_binding, script.name, 1) }.call
            Script.current.get_next_label
          end
        rescue SystemExit
          nil
        rescue SyntaxError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue ScriptError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue NoMemoryError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue LoadError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue SecurityError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          if (name = Script.current.name)
            respond "--- Lich: review this script (#{name}) to make sure it isn't malicious, and type #{$clean_lich_char}trust #{name}"
          end
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue ThreadError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue SystemStackError
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        rescue StandardError # Exception
          if $! == JUMP
            retry if Script.current.get_next_label != JUMP_ERROR
            respond "--- label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!"
            respond $!.backtrace.first
            Lich.log "label error: `#{Script.current.jump_label}' was not found, and no `LabelError' label was found!\n\t#{$!.backtrace.join("\n\t")}"
            Script.current.kill
          else
            respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
            Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
          end
        rescue
          respond "--- Lich: error: #{$!}\n\t#{$!.backtrace[0..1].join("\n\t")}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        ensure
          Script.current.kill
        end
      end
    else
      respond '--- error: out of cheese'
    end
  }
  script_obj.thread_group.add(new_thread)
  script_obj
}
@@elevated_exists =

Checks if an elevated script exists.

Examples:

exists = Script.elevated_exists('my_script.lic')

Returns:

  • (Boolean, nil)

    Returns true if the script exists, false if it does not, or nil if the name is invalid.

proc { |script_name|
  if script_name =~ /\\|\//
    nil
  elsif script_name =~ /\.(?:lic|lich|rb|cmd|wiz)(?:\.gz)?$/i
    File.exist?("#{SCRIPT_DIR}/#{script_name}") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}")
  else
    File.exist?("#{SCRIPT_DIR}/#{script_name}.lic") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.lic") ||
      File.exist?("#{SCRIPT_DIR}/#{script_name}.lich") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.lich") ||
      File.exist?("#{SCRIPT_DIR}/#{script_name}.rb") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.rb") ||
      File.exist?("#{SCRIPT_DIR}/#{script_name}.cmd") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.cmd") ||
      File.exist?("#{SCRIPT_DIR}/#{script_name}.wiz") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.wiz") ||
      File.exist?("#{SCRIPT_DIR}/#{script_name}.lic.gz") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.lic.gz") ||
      File.exist?("#{SCRIPT_DIR}/#{script_name}.rb.gz") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.rb.gz") ||
      File.exist?("#{SCRIPT_DIR}/#{script_name}.cmd.gz") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.cmd.gz") ||
      File.exist?("#{SCRIPT_DIR}/#{script_name}.wiz.gz") || File.exist?("#{SCRIPT_DIR}/custom/#{script_name}.wiz.gz")
  end
}
@@elevated_log =

Logs data from the current script to a log file.

Examples:

Script.elevated_log("This is a log entry.")

Returns:

  • (Boolean)

    Returns true if logging was successful, false otherwise.

Raises:

  • (StandardError)

    Raises an error if the script cannot be identified or if logging fails.

proc { |data|
  if (script = Script.current)
    if script.name =~ /\\|\//
      nil
    else
      begin
        Dir.mkdir("#{LICH_DIR}/logs") unless File.exist?("#{LICH_DIR}/logs")
        File.open("#{LICH_DIR}/logs/#{script.name}.log", 'a') { |f| f.puts data }
        true
      rescue
        respond "--- Lich: error: Script.log: #{$!}"
        false
      end
    end
  else
    respond '--- error: Script.log: unable to identify calling script'
    false
  end
}
@@elevated_db =

Creates a new database for the current script.

Examples:

db = Script.elevated_db

Returns:

  • (SQLite3::Database, nil)

    Returns a new SQLite3 database object or nil if the script is invalid.

Raises:

  • (StandardError)

    Raises an error if the script cannot be identified or if the database cannot be created.

proc {
  if (script = Script.current)
    if script.name =~ /^lich$/i
      respond '--- error: Script.db cannot be used by a script named lich'
      nil
    elsif script.class == ExecScript
      respond '--- error: Script.db cannot be used by exec scripts'
      nil
    else
      SQLite3::Database.new("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.db3")
    end
  else
    respond '--- error: Script.db called by an unknown script'
    nil
  end
}
@@elevated_open_file =
Note:

This proc will respond with an error message if the current script is restricted from opening files.

A proc that handles opening files with specific restrictions based on the current script.

Examples:

file = @@elevated_open_file.call('txt', 'r') # Opens a text file for reading.

Returns:

  • (File, SQLite3::Database, nil)

    the opened file or database, or nil if an error occurs.

Raises:

  • (StandardError)

    raises an error if the script name is ‘lich’, ‘entry’, or if it is an ExecScript.

proc { |ext, mode, _block|
  if (script = Script.current)
    if script.name =~ /^lich$/i
      respond '--- error: Script.open_file cannot be used by a script named lich'
      nil
    elsif script.name =~ /^entry$/i
      respond '--- error: Script.open_file cannot be used by a script named entry'
      nil
    elsif script.class == ExecScript
      respond '--- error: Script.open_file cannot be used by exec scripts'
      nil
    elsif ext.downcase == 'db3'
      SQLite3::Database.new("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.db3")
      # fixme: block gets elevated... why?
      #         elsif block
      #            File.open("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.#{ext.gsub(/\/|\\/, '_')}", mode, &block)
    else
      File.open("#{DATA_DIR}/#{script.name.gsub(/\/|\\/, '_')}.#{ext.gsub(/\/|\\/, '_')}", mode)
    end
  else
    respond '--- error: Script.open_file called by an unknown script'
    nil
  end
}
@@running =
Array.new

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ void

Initializes a new instance of the class.

Examples:

instance = MyClass.new(file: 'script.txt', args: 'arg1 arg2', quiet: false)

Parameters:

  • args (Hash)

    a hash containing initialization parameters

Options Hash (args):

  • :file (String)

    the name of the file to read

  • :args (String, Array)

    additional arguments to process

  • :quiet (Boolean)

    whether to suppress output

Raises:

  • (StandardError)

    if there is an error reading the script file



885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
# File 'lib/common/script.rb', line 885

def initialize(args)
  @file_name = args[:file]
  @name = /.*[\/\\]+([^\.]+)\./.match(@file_name).captures.first
  if args[:args].class == String
    if args[:args].empty?
      @vars = Array.new
    else
      @vars = [args[:args]]
      @vars.concat(args[:args].scan(/[^\s"]*(?<!\\)"(?:\\"|[^"])+(?<!\\)"[^\s]*|(?:\\"|[^"\s])+/).collect { |s| s.gsub(/(?<!\\)"/, '').gsub('\\"', '"') })
    end
  elsif args[:args].class == Array
    unless (args[:args].nil? || args[:args].empty?)
      @vars = [args[:args].join(" ")]
      @vars.concat args[:args]
    else
      @vars = Array.new
    end
  else
    @vars = Array.new
  end
  @quiet = (args[:quiet] ? true : false)
  @downstream_buffer = LimitedArray.new
  @want_downstream = true
  @want_downstream_xml = false
  @want_script_output = false
  @upstream_buffer = LimitedArray.new
  @want_upstream = false
  @unique_buffer = LimitedArray.new
  @watchfor = Hash.new
  @at_exit_procs = Array.new
  @die_with = Array.new
  @paused = false
  @hidden = false
  @no_pause_all = false
  @no_kill_all = false
  @silent = false
  @safe = false
  @no_echo = false
  @match_stack_labels = Array.new
  @match_stack_strings = Array.new
  @label_order = Array.new
  @labels = Hash.new
  @killer_mutex = Mutex.new
  @ignore_pause = false
  data = nil
  if @file_name =~ /\.gz$/i
    begin
      Zlib::GzipReader.open(@file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
    rescue
      respond "--- Lich: error reading script file (#{@file_name}): #{$!}"
      # return nil
    end
  else
    begin
      File.open(@file_name) { |f| data = f.readlines.collect { |line| line.chomp } }
    rescue
      respond "--- Lich: error reading script file (#{@file_name}): #{$!}"
      # return nil
    end
  end
  @quiet = true if data[0] =~ /^[\t\s]*#?[\t\s]*(?:quiet|hush)$/i
  @current_label = '~start'
  @labels[@current_label] = String.new
  @label_order.push(@current_label)
  for line in data
    if line =~ /^([\d_\w]+):$/
      @current_label = $1
      @label_order.push(@current_label)
      @labels[@current_label] = String.new
    else
      @labels[@current_label].concat "#{line}\n"
    end
  end
  data = nil
  @current_label = @label_order[0]
  @thread_group = ThreadGroup.new
  @@running.push(self)
  # return self
end

Instance Attribute Details

#at_exit_procsObject (readonly)

Returns the value of attribute at_exit_procs.



# File 'lib/common/script.rb', line 350

#command_lineObject

Returns the value of attribute command_line.



409
410
411
# File 'lib/common/script.rb', line 409

def command_line
  @command_line
end

#current_labelObject

Returns the value of attribute current_label.



409
410
411
# File 'lib/common/script.rb', line 409

def current_label
  @current_label
end

#die_withObject

Returns the value of attribute die_with.



409
410
411
# File 'lib/common/script.rb', line 409

def die_with
  @die_with
end

#downstream_bufferObject

Returns the value of attribute downstream_buffer.



409
410
411
# File 'lib/common/script.rb', line 409

def downstream_buffer
  @downstream_buffer
end

#file_nameObject (readonly)

Returns the value of attribute file_name.



# File 'lib/common/script.rb', line 350

#hiddenObject

Returns the value of attribute hidden.



409
410
411
# File 'lib/common/script.rb', line 409

def hidden
  @hidden
end

#ignore_pauseObject

Returns the value of attribute ignore_pause.



409
410
411
# File 'lib/common/script.rb', line 409

def ignore_pause
  @ignore_pause
end

#jump_labelObject

Returns the value of attribute jump_label.



409
410
411
# File 'lib/common/script.rb', line 409

def jump_label
  @jump_label
end

#label_orderObject (readonly)

Returns the value of attribute label_order.



# File 'lib/common/script.rb', line 350

#match_stack_labelsObject

Returns the value of attribute match_stack_labels.



409
410
411
# File 'lib/common/script.rb', line 409

def match_stack_labels
  @match_stack_labels
end

#match_stack_stringsObject

Returns the value of attribute match_stack_strings.



409
410
411
# File 'lib/common/script.rb', line 409

def match_stack_strings
  @match_stack_strings
end

#nameObject (readonly)

Returns the value of attribute name.



# File 'lib/common/script.rb', line 350

#no_echoObject

Returns the value of attribute no_echo.



409
410
411
# File 'lib/common/script.rb', line 409

def no_echo
  @no_echo
end

#no_kill_allObject

Returns the value of attribute no_kill_all.



409
410
411
# File 'lib/common/script.rb', line 409

def no_kill_all
  @no_kill_all
end

#no_pause_allObject

Returns the value of attribute no_pause_all.



409
410
411
# File 'lib/common/script.rb', line 409

def no_pause_all
  @no_pause_all
end

#pausedObject

Returns the value of attribute paused.



409
410
411
# File 'lib/common/script.rb', line 409

def paused
  @paused
end

#quietObject

Returns the value of attribute quiet.



409
410
411
# File 'lib/common/script.rb', line 409

def quiet
  @quiet
end

#safeObject (readonly)

Returns the value of attribute safe.



# File 'lib/common/script.rb', line 350

#silentObject

Returns the value of attribute silent.



409
410
411
# File 'lib/common/script.rb', line 409

def silent
  @silent
end

#unique_bufferObject

Returns the value of attribute unique_buffer.



409
410
411
# File 'lib/common/script.rb', line 409

def unique_buffer
  @unique_buffer
end

#upstream_bufferObject

Returns the value of attribute upstream_buffer.



409
410
411
# File 'lib/common/script.rb', line 409

def upstream_buffer
  @upstream_buffer
end

#varsObject (readonly)

Returns the value of attribute vars.



# File 'lib/common/script.rb', line 350

#want_downstreamObject

Returns the value of attribute want_downstream.



409
410
411
# File 'lib/common/script.rb', line 409

def want_downstream
  @want_downstream
end

#want_downstream_xmlObject

Returns the value of attribute want_downstream_xml.



409
410
411
# File 'lib/common/script.rb', line 409

def want_downstream_xml
  @want_downstream_xml
end

#want_script_outputObject

Returns the value of attribute want_script_output.



409
410
411
# File 'lib/common/script.rb', line 409

def want_script_output
  @want_script_output
end

#want_upstreamObject

Returns the value of attribute want_upstream.



409
410
411
# File 'lib/common/script.rb', line 409

def want_upstream
  @want_upstream
end

#watchforObject

Returns the value of attribute watchfor.



409
410
411
# File 'lib/common/script.rb', line 409

def watchfor
  @watchfor
end

Class Method Details

.at_exit(&block) ⇒ Boolean

Registers a block to be executed at exit for the current script.

Examples:

Script.at_exit { puts "Exiting script." }

Parameters:

  • block (Proc)

    the block to execute at exit

Returns:

  • (Boolean)

    true if the block was registered, false if no script is identified



685
686
687
688
689
690
691
692
# File 'lib/common/script.rb', line 685

def Script.at_exit(&block)
  if (script = Script.current)
    script.at_exit(&block)
  else
    respond "--- Lich: error: Script.at_exit: can't identify calling script"
    return false
  end
end

.clear_exit_procsBoolean

Clears all exit procs for the current script.

Examples:

Script.clear_exit_procs

Returns:

  • (Boolean)

    true if cleared, false if no script is identified



699
700
701
702
703
704
705
706
# File 'lib/common/script.rb', line 699

def Script.clear_exit_procs
  if (script = Script.current)
    script.clear_exit_procs
  else
    respond "--- Lich: error: Script.clear_exit_procs: can't identify calling script"
    return false
  end
end

.currentScript?

Retrieves the current script.

Examples:

current_script = Script.current

Returns:

  • (Script, nil)

    the current script or nil if none is running



470
471
472
473
474
475
476
477
# File 'lib/common/script.rb', line 470

def Script.current
  if (script = @@running.find { |s| s.has_thread?(Thread.current) })
    sleep 0.2 while script.paused? and not script.ignore_pause
    script
  else
    nil
  end
end

.dbObject

Retrieves the database connection using the elevated mechanism.

Examples:

db_connection = Script.db

Returns:

  • (Object)

    the database connection



663
664
665
# File 'lib/common/script.rb', line 663

def Script.db
  @@elevated_db.call
end

.distrust(_script_name) ⇒ Boolean

Distrusts a script by returning false, as no actual distrust mechanism is implemented in this version.

Examples:

Script.distrust("my_script")

Parameters:

  • _script_name (String)

    The name of the script to distrust (ignored).

Returns:

  • (Boolean)

    Always returns false.



808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
# File 'lib/common/script.rb', line 808

def Script.distrust(script_name)
  begin
    there = Lich.db.get_first_value('SELECT name FROM trusted_scripts WHERE name=?;', [script_name.encode('UTF-8')])
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
  if there
    begin
      Lich.db.execute('DELETE FROM trusted_scripts WHERE name=?;', [script_name.encode('UTF-8')])
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    true
  else
    false
  end
end

.exists?(script_name) ⇒ Boolean

Checks if a script exists by name.

Examples:

exists = Script.exists?("example_script")

Parameters:

  • script_name (String)

    the name of the script to check

Returns:

  • (Boolean)

    true if the script exists, false otherwise



581
582
583
# File 'lib/common/script.rb', line 581

def Script.exists?(script_name)
  @@elevated_exists.call(script_name)
end

.exit!Boolean

Exits the current script.

Examples:

Script.exit!

Returns:

  • (Boolean)

    true if exited, false if no script is identified



713
714
715
716
717
718
719
720
# File 'lib/common/script.rb', line 713

def Script.exit!
  if (script = Script.current)
    script.exit!
  else
    respond "--- Lich: error: Script.exit!: can't identify calling script"
    return false
  end
end

.hiddenArray

Lists all running scripts that are hidden.

Examples:

hidden_scripts = Script.hidden

Returns:

  • (Array)

    an array of hidden running scripts



758
759
760
761
762
763
764
# File 'lib/common/script.rb', line 758

def Script.hidden
  list = Array.new
  for script in @@running
    list.push(script) if script.hidden
  end
  return list
end

.indexArray

Retrieves the index of running scripts.

Examples:

running_scripts = Script.index

Returns:

  • (Array)

    an array of running scripts



749
750
751
# File 'lib/common/script.rb', line 749

def Script.index
  Script.running
end

.kill(name) ⇒ Boolean

Kills a running script by name.

Examples:

was_killed = Script.kill("example_script")

Parameters:

  • name (String)

    the name of the script to kill

Returns:

  • (Boolean)

    true if the script was killed, false if not found



552
553
554
555
556
557
558
559
# File 'lib/common/script.rb', line 552

def Script.kill(name)
  if (s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i }))
    s.kill
    true
  else
    false
  end
end

.listArray

Lists all currently running scripts.

Examples:

running_scripts = Script.list

Returns:

  • (Array)

    an array of currently running scripts



461
462
463
# File 'lib/common/script.rb', line 461

def Script.list
  @@running.dup
end

.list_trustedArray<String>

Lists trusted scripts, which will always return an empty array in this version.

Examples:

trusted_scripts = Script.list_trusted

Returns:

  • (Array<String>)

    Always returns an empty array.



834
835
836
837
838
839
840
841
842
843
# File 'lib/common/script.rb', line 834

def Script.list_trusted
  list = Array.new
  begin
    Lich.db.execute('SELECT name FROM trusted_scripts;').each { |name| list.push(name[0]) }
  rescue SQLite3::BusyException
    sleep 0.1
    retry
  end
  list
end

.log(data) ⇒ void

This method returns an undefined value.

Logs data using the elevated logging mechanism.

Examples:

Script.log("This is a log message.")

Parameters:

  • data (String)

    the data to log



654
655
656
# File 'lib/common/script.rb', line 654

def Script.log(data)
  @@elevated_log.call(data)
end

.namescript_incoming(line) ⇒ void

This method returns an undefined value.

Sends a new downstream line to all running scripts.

Examples:

Script.namescript_incoming("incoming data")

Parameters:

  • line (String)

    the line of data to send



772
773
774
# File 'lib/common/script.rb', line 772

def Script.namescript_incoming(line)
  Script.new_downstream(line)
end

.new_downstream(line) ⇒ void

This method returns an undefined value.

Sends a new downstream line to all running scripts that want it.

Examples:

Script.new_downstream("example data")

Parameters:

  • line (String)

    the line of data to send downstream



615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/common/script.rb', line 615

def Script.new_downstream(line)
  @@running.each { |script|
    script.downstream_buffer.push(line.chomp) if script.want_downstream
    unless script.watchfor.empty?
      script.watchfor.each_pair { |trigger, action|
        if line =~ trigger
          new_thread = Thread.new {
            sleep 0.011 until Script.current
            begin
              action.call
            rescue
              echo "watchfor error: #{$!}"
            end
          }
          script.thread_group.add(new_thread)
        end
      }
    end
  }
end

.new_downstream_xml(line) ⇒ void

This method returns an undefined value.

Sends a new downstream XML line to all running scripts that want it.

Examples:

Script.new_downstream_xml("<data>example</data>")

Parameters:

  • line (String)

    the line of XML data to send



591
592
593
594
595
# File 'lib/common/script.rb', line 591

def Script.new_downstream_xml(line)
  for script in @@running
    script.downstream_buffer.push(line.chomp) if script.want_downstream_xml
  end
end

.new_script_output(line) ⇒ void

This method returns an undefined value.

Sends a new script output line to all running scripts that want it.

Examples:

Script.new_script_output("output data")

Parameters:

  • line (String)

    the line of output data to send



642
643
644
645
646
# File 'lib/common/script.rb', line 642

def Script.new_script_output(line)
  for script in @@running
    script.downstream_buffer.push(line.chomp) if script.want_script_output
  end
end

.new_upstream(line) ⇒ void

This method returns an undefined value.

Sends a new upstream line to all running scripts that want it.

Examples:

Script.new_upstream("example data")

Parameters:

  • line (String)

    the line of data to send upstream



603
604
605
606
607
# File 'lib/common/script.rb', line 603

def Script.new_upstream(line)
  for script in @@running
    script.upstream_buffer.push(line.chomp) if script.want_upstream
  end
end

.open_file(ext, mode = 'r', &block) ⇒ void

This method returns an undefined value.

Opens a file with the specified extension and mode using the elevated mechanism.

Examples:

Script.open_file("txt") { |file| puts file.read }

Parameters:

  • ext (String)

    the file extension

  • mode (String) (defaults to: 'r')

    the mode to open the file in (default: ‘r’)

  • block (Proc)

    the block to execute with the opened file



675
676
677
# File 'lib/common/script.rb', line 675

def Script.open_file(ext, mode = 'r', &block)
  @@elevated_open_file.call(ext, mode, block)
end

.pause(name = nil) ⇒ Script, Boolean

Pauses a running script by name.

Examples:

Script.pause("example_script")

Parameters:

  • name (String, nil) (defaults to: nil)

    the name of the script to pause (optional)

Returns:

  • (Script, Boolean)

    the paused script or true if paused successfully, false if not found



517
518
519
520
521
522
523
524
525
526
527
528
529
# File 'lib/common/script.rb', line 517

def Script.pause(name = nil)
  if name.nil?
    Script.current.pause
    Script.current
  else
    if (s = (@@running.find { |i| (i.name == name) and not i.paused? }) || (@@running.find { |i| (i.name =~ /^#{name}$/i) and not i.paused? }))
      s.pause
      true
    else
      false
    end
  end
end

.paused?(name) ⇒ Boolean?

Checks if a script is paused by name.

Examples:

is_paused = Script.paused?("example_script")

Parameters:

  • name (String)

    the name of the script to check

Returns:

  • (Boolean, nil)

    true if the script is paused, false if not, nil if not found



567
568
569
570
571
572
573
# File 'lib/common/script.rb', line 567

def Script.paused?(name)
  if (s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i }))
    s.paused?
  else
    nil
  end
end

.run(*args) ⇒ void

This method returns an undefined value.

Runs a script with the given arguments.

Examples:

Script.run("arg1", "arg2")

Parameters:

  • args (Array)

    the arguments to pass to the script



495
496
497
498
499
# File 'lib/common/script.rb', line 495

def Script.run(*args)
  if (s = @@elevated_script_start.call(args))
    sleep 0.1 while @@running.include?(s)
  end
end

.runningArray

Lists all running scripts that are not hidden.

Examples:

visible_scripts = Script.running

Returns:

  • (Array)

    an array of visible running scripts



736
737
738
739
740
741
742
# File 'lib/common/script.rb', line 736

def Script.running
  list = Array.new
  for script in @@running
    list.push(script) unless script.hidden
  end
  return list
end

.running?(name) ⇒ Boolean

Checks if a script with the given name is currently running.

Examples:

is_running = Script.running?("example_script")

Parameters:

  • name (String)

    the name of the script to check

Returns:

  • (Boolean)

    true if the script is running, false otherwise



507
508
509
# File 'lib/common/script.rb', line 507

def Script.running?(name)
  @@running.any? { |i| (i.name =~ /^#{name}$/i) }
end

.selfScript

Retrieves the current script.

Examples:

current_script = Script.self

Returns:

  • (Script)

    the current script



727
728
729
# File 'lib/common/script.rb', line 727

def Script.self
  Script.current
end

.start(*args) ⇒ void

This method returns an undefined value.

Starts a new script with the given arguments.

Examples:

Script.start("arg1", "arg2")

Parameters:

  • args (Array)

    the arguments to pass to the script



485
486
487
# File 'lib/common/script.rb', line 485

def Script.start(*args)
  @@elevated_script_start.call(args)
end

.trust(_script_name) ⇒ Boolean

Trusts a script by returning true, as no actual trust mechanism is implemented in this version.

Examples:

Script.trust("my_script")

Parameters:

  • _script_name (String)

    The name of the script to trust (ignored).

Returns:

  • (Boolean)

    Always returns true.



785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
# File 'lib/common/script.rb', line 785

def Script.trust(script_name)
  # fixme: case sensitive blah blah
  if not caller.any? { |c| c =~ /eval|run/ }
    begin
      Lich.db.execute('INSERT OR REPLACE INTO trusted_scripts(name) values(?);', [script_name.encode('UTF-8')])
    rescue SQLite3::BusyException
      sleep 0.1
      retry
    end
    true
  else
    respond '--- error: scripts may not trust scripts'
    false
  end
end

.unpause(name) ⇒ Boolean

Unpauses a running script by name.

Examples:

was_unpaused = Script.unpause("example_script")

Parameters:

  • name (String)

    the name of the script to unpause

Returns:

  • (Boolean)

    true if the script was unpaused, false if not found



537
538
539
540
541
542
543
544
# File 'lib/common/script.rb', line 537

def Script.unpause(name)
  if (s = (@@running.find { |i| (i.name == name) and i.paused? }) || (@@running.find { |i| (i.name =~ /^#{name}$/i) and i.paused? }))
    s.unpause
    true
  else
    false
  end
end

.version(script_name, script_version_required = nil) ⇒ Gem::Version?

Returns the version of the script or nil if not found.

Examples:

version = Script.version("example_script", "1.0.0")

Parameters:

  • script_name (String)

    the name of the script

  • script_version_required (String, nil) (defaults to: nil)

    the required version of the script (optional)

Returns:

  • (Gem::Version, nil)

    the version of the script or nil if not found

Raises:

  • (Errno::ENOENT)

    if the script file cannot be found



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/common/script.rb', line 419

def Script.version(script_name, script_version_required = nil)
  script_name = script_name.sub(/[.](lic|rb|cmd|wiz)$/, '')
  file_list = Dir.children(File.join(SCRIPT_DIR, "custom")).sort_by { |fn| fn.sub(/[.](lic|rb|cmd|wiz)$/, '') }.map { |s| s.prepend("/custom/") } + Dir.children(SCRIPT_DIR).sort_by { |fn| fn.sub(/[.](lic|rb|cmd|wiz)$/, '') }
  if (file_name = (file_list.find { |val| val =~ /^(?:\/custom\/)?#{Regexp.escape(script_name)}\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/ || val =~ /^(?:\/custom\/)?#{Regexp.escape(script_name)}\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/i } || file_list.find { |val| val =~ /^(?:\/custom\/)?#{Regexp.escape(script_name)}[^.]+\.(?i:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/ } || file_list.find { |val| val =~ /^(?:\/custom\/)?#{Regexp.escape(script_name)}[^.]+\.(?:lic|rb|cmd|wiz)(?:\.gz|\.Z)?$/i }))
    script_name = file_name.sub(/\..{1,3}$/, '')
  end
  if file_name.nil?
    respond "--- Lich: could not find script '#{script_name}' in directory #{SCRIPT_DIR}"
    return nil
  end

  script_version = '0.0.0'
  script_data = File.open("#{SCRIPT_DIR}/#{file_name}", 'r').read
  if script_data =~ /^=begin\r?\n?(.+?)^=end/m
    comments = $1.split("\n")
  else
    comments = []
    script_data.split("\n").each { |line|
      if line =~ /^[\t\s]*#/
        comments.push(line)
      elsif line !~ /^[\t\s]*$/
        break
      end
    }
  end
  for line in comments
    if line =~ /^[\s\t#]*version:[\s\t]*([\w,\s\.\d]+)/i
      script_version = $1.sub(/\s\(.*?\)/, '').strip
    end
  end
  if script_version_required
    Gem::Version.new(script_version) < Gem::Version.new(script_version_required)
  else
    Gem::Version.new(script_version)
  end
end

Instance Method Details

#at_exit(&block) ⇒ Boolean

Registers a block to be executed at script exit.

Examples:

script.at_exit { puts "Script is exiting." }

Parameters:

  • block (Proc)

    the block of code to execute at exit

Returns:

  • (Boolean)

    true if the block was added, false otherwise



1005
1006
1007
1008
1009
1010
1011
1012
1013
# File 'lib/common/script.rb', line 1005

def at_exit(&block)
  if block
    @at_exit_procs.push(block)
    return true
  else
    respond '--- warning: Script.at_exit called with no code block'
    return false
  end
end

#clearArray

Clears the downstream buffer and returns its contents.

Examples:

contents = script.clear

Returns:

  • (Array)

    the contents of the downstream buffer before clearing



1155
1156
1157
1158
1159
# File 'lib/common/script.rb', line 1155

def clear
  to_return = @downstream_buffer.dup
  @downstream_buffer.clear
  to_return
end

#clear_exit_procsBoolean

Clears all exit procedures registered with at_exit.

Examples:

script.clear_exit_procs

Returns:

  • (Boolean)

    true if the exit procedures were cleared



1020
1021
1022
1023
# File 'lib/common/script.rb', line 1020

def clear_exit_procs
  @at_exit_procs.clear
  true
end

#exitvoid

This method returns an undefined value.

Exits the script by calling kill.

Examples:

script.exit


1030
1031
1032
# File 'lib/common/script.rb', line 1030

def exit
  kill
end

#exit!void

This method returns an undefined value.

Exits the script and clears exit procedures.

Examples:

script.exit!


1039
1040
1041
1042
# File 'lib/common/script.rb', line 1039

def exit!
  @at_exit_procs.clear
  kill
end

#feedme_upstreamvoid

This method returns an undefined value.

Toggles the upstream feed state.

Examples:

script.feedme_upstream


1266
1267
1268
# File 'lib/common/script.rb', line 1266

def feedme_upstream
  @want_upstream = !@want_upstream
end

#get_next_labelString?

Retrieves the next label in the label order.

Examples:

next_label = script.get_next_label

Returns:

  • (String, nil)

    the next label or nil if none exists

Raises:

  • (StandardError)

    if there is a jump error



1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
# File 'lib/common/script.rb', line 1131

def get_next_label
  if !@jump_label
    @current_label = @label_order[@label_order.index(@current_label) + 1]
  else
    if (label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/ })
      @current_label = label
    elsif (label = @labels.keys.find { |val| val =~ /^#{@jump_label}$/i })
      @current_label = label
    elsif (label = @labels.keys.find { |val| val =~ /^labelerror$/i })
      @current_label = label
    else
      @current_label = nil
      return JUMP_ERROR
    end
    @jump_label = nil
    @current_label
  end
end

#getsString, false

Retrieves the next item from the downstream buffer.

Examples:

item = script.gets

Returns:

  • (String, false)

    the next item or false if waiting for data



1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
# File 'lib/common/script.rb', line 1175

def gets
  # fixme: no xml gets
  if @want_downstream or @want_downstream_xml or @want_script_output
    sleep 0.05 while @downstream_buffer.empty?
    @downstream_buffer.shift
  else
    echo 'this script is set as unique but is waiting for game data...'
    sleep 2
    false
  end
end

#gets?String?

Checks if there is an item available in the downstream buffer.

Examples:

item = script.gets?

Returns:

  • (String, nil)

    the next item or nil if none exists



1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
# File 'lib/common/script.rb', line 1192

def gets?
  if @want_downstream or @want_downstream_xml or @want_script_output
    if @downstream_buffer.empty?
      nil
    else
      @downstream_buffer.shift
    end
  else
    echo 'this script is set as unique but is waiting for game data...'
    sleep 2
    false
  end
end

#has_thread?(t) ⇒ Boolean

Checks if a specific thread is part of the thread group.

Examples:

script.has_thread?(some_thread)

Parameters:

  • t (Thread)

    the thread to check

Returns:

  • (Boolean)

    true if the thread is in the group, false otherwise



1084
1085
1086
# File 'lib/common/script.rb', line 1084

def has_thread?(t)
  @thread_group.list.include?(t)
end

#instance_eval(*_a) ⇒ nil

Returns nil for any evaluated instance.

Examples:

script.instance_eval { some_method }

Parameters:

  • _a (Array)

    ignored parameters

Returns:

  • (nil)


1058
# File 'lib/common/script.rb', line 1058

def instance_eval(*_a);         nil; end

#instance_variable_get(*_a) ⇒ nil

Returns nil for any instance variable.

Examples:

script.instance_variable_get(:@var)

Parameters:

  • _a (Array)

    ignored parameters

Returns:

  • (nil)


1050
# File 'lib/common/script.rb', line 1050

def instance_variable_get(*_a); nil; end

#killString

Kills the current script and cleans up resources.

Examples:

script.kill

Returns:

  • (String)

    the name of the script that was killed

Raises:

  • (StandardError)

    if there is an error during the kill process



971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
# File 'lib/common/script.rb', line 971

def kill
  Thread.new {
    @killer_mutex.synchronize {
      if @@running.include?(self)
        begin
          @thread_group.list.dup.each { |t|
            unless t == Thread.current
              t.kill rescue nil
            end
          }
          @thread_group.add(Thread.current)
          @die_with.each { |script_name| Script.kill(script_name) }
          @paused = false
          @at_exit_procs.each { |p| report_errors { p.call } }
          @die_with = @at_exit_procs = @downstream_buffer = @upstream_buffer = @match_stack_labels = @match_stack_strings = nil
          @@running.delete(self)
          respond("--- Lich: #{@name} has exited.") unless @quiet
          GC.start
        rescue
          respond "--- Lich: error: #{$!}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        end
      end
    }
  }
  @name
end

#labelsHash

Returns the labels hash.

Examples:

labels = script.labels

Returns:

  • (Hash)

    the labels associated with the script



1065
1066
1067
# File 'lib/common/script.rb', line 1065

def labels
  @labels
end

#match_stack_add(label, string) ⇒ void

This method returns an undefined value.

Adds a label and string to the match stack.

Examples:

script.match_stack_add('label1', 'some string')

Parameters:

  • label (String)

    the label to add

  • string (String)

    the string to add



1277
1278
1279
1280
# File 'lib/common/script.rb', line 1277

def match_stack_add(label, string)
  @match_stack_labels.push(label)
  @match_stack_strings.push(string)
end

#match_stack_clearvoid

This method returns an undefined value.

Clears the match stack.

Examples:

script.match_stack_clear


1287
1288
1289
1290
# File 'lib/common/script.rb', line 1287

def match_stack_clear
  @match_stack_labels.clear
  @match_stack_strings.clear
end

#pausevoid

This method returns an undefined value.

Pauses the script if it is not already paused.

Examples:

script.pause


1093
1094
1095
1096
1097
1098
1099
1100
# File 'lib/common/script.rb', line 1093

def pause
  if @paused == true
    respond "--- Lich: #{@name} is already paused."
  else
    respond "--- Lich: #{@name} paused."
    @paused = true
  end
end

#paused?Boolean

Checks if the script is currently paused.

Examples:

is_paused = script.paused?

Returns:

  • (Boolean)

    true if paused, false otherwise



1121
1122
1123
# File 'lib/common/script.rb', line 1121

def paused?
  @paused
end

#safe?Boolean

Checks if the script is in a safe state.

Examples:

is_safe = script.safe?

Returns:

  • (Boolean)

    true if safe, false otherwise



1257
1258
1259
# File 'lib/common/script.rb', line 1257

def safe?
  @safe
end

#thread_groupThreadGroup

Returns the thread group associated with the script.

Examples:

group = script.thread_group

Returns:

  • (ThreadGroup)

    the thread group for managing threads



1074
1075
1076
# File 'lib/common/script.rb', line 1074

def thread_group
  @thread_group
end

#to_sString

Returns the string representation of the script.

Examples:

name = script.to_s

Returns:

  • (String)

    the name of the script



1166
1167
1168
# File 'lib/common/script.rb', line 1166

def to_s
  @name
end

#unique_getsString

Retrieves the next item from the unique buffer.

Examples:

item = script.unique_gets

Returns:

  • (String)

    the next item from the unique buffer



1234
1235
1236
1237
# File 'lib/common/script.rb', line 1234

def unique_gets
  sleep 0.05 while @unique_buffer.empty?
  @unique_buffer.shift
end

#unique_gets?String?

Checks if there is an item available in the unique buffer.

Examples:

item = script.unique_gets?

Returns:

  • (String, nil)

    the next item or nil if none exists



1244
1245
1246
1247
1248
1249
1250
# File 'lib/common/script.rb', line 1244

def unique_gets?
  if @unique_buffer.empty?
    nil
  else
    @unique_buffer.shift
  end
end

#unpausevoid

This method returns an undefined value.

Unpauses the script if it is currently paused.

Examples:

script.unpause


1107
1108
1109
1110
1111
1112
1113
1114
# File 'lib/common/script.rb', line 1107

def unpause
  if @paused == true
    respond "--- Lich: #{@name} unpaused."
    @paused = false
  else
    respond "--- Lich: #{@name} is not paused."
  end
end

#upstream_getsString

Retrieves the next item from the upstream buffer.

Examples:

item = script.upstream_gets

Returns:

  • (String)

    the next item from the upstream buffer



1211
1212
1213
1214
# File 'lib/common/script.rb', line 1211

def upstream_gets
  sleep 0.05 while @upstream_buffer.empty?
  @upstream_buffer.shift
end

#upstream_gets?String?

Checks if there is an item available in the upstream buffer.

Examples:

item = script.upstream_gets?

Returns:

  • (String, nil)

    the next item or nil if none exists



1221
1222
1223
1224
1225
1226
1227
# File 'lib/common/script.rb', line 1221

def upstream_gets?
  if @upstream_buffer.empty?
    nil
  else
    @upstream_buffer.shift
  end
end