Class: Lich::Common::Script

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

Overview

Represents a script that can be executed within the Lich engine.

This class manages the execution state, variables, and script flow.

See Also:

  • for script execution details.

Direct Known Subclasses

ExecScript, WizardScript

Defined Under Namespace

Classes: JumpError

Constant Summary collapse

JUMP =
JumpError.exception('JUMP')
JUMP_ERROR =
JumpError.exception('JUMP_ERROR')
@@elevated_script_start =
proc { |args|
  if args.empty?
    # fixme: error
    next nil
  elsif args[0].is_a?(String)
    script_name = args[0]
    if args[1]
      if args[1].is_a?(String)
        script_args = args[1]
        if args[2]
          if args[2].is_a?(Hash)
            options = args[2]
          else
            # fixme: error
            next nil
          end
        end
      elsif args[1].is_a?(Hash)
        options = args[1]
        script_args = (options[:args] || String.new)
      else
        # fixme: error
        next nil
      end
    else
      options = Hash.new
    end
  elsif args[0].is_a?(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
  # Build file list: custom/ root, then custom/ subdirectories, then SCRIPT_DIR root
  custom_base = File.join(SCRIPT_DIR, "custom")
  custom_dirs = []
  if File.directory?(custom_base)
    custom_dirs << custom_base
    Dir.children(custom_base).sort.each do |child|
      child_path = File.join(custom_base, child)
      custom_dirs << child_path if File.directory?(child_path)
    end
  end
  file_list = custom_dirs.flat_map { |dir|
    prefix = dir.sub(SCRIPT_DIR, '')
    Dir.children(dir)
       .select { |f| f =~ /\.(lic|rb|cmd|wiz)(\.(gz|Z))?$/i }
       .sort_by { |fn| fn.sub(/\.[^.]+$/, '') }
       .map { |s| "#{prefix}/#{s}" }
  } + 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(%r{/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.custom? ? 'custom/' : ''}#{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 JumpError
          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 StandardError
          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 JumpError
          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 StandardError
          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 =
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 =
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 =
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.is_a?(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 =
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.is_a?(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) ⇒ Script

Returns a new instance of Script.



639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
# File 'documented/common/script.rb', line 639

def initialize(args)
  @file_name = args[:file]
  @name = /.*[\/\\]+([^\.]+)\./.match(@file_name).captures.first
  @custom = (/.*[\/\\]+(custom)[\/\\]+[^\.]+\./.match(@file_name).captures.nil? ? false : true)
  if args[:args].is_a?(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].is_a?(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
  @downstream_buffer.max_size = 400
  @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
  @killed_externally = false
  @kill_source = nil
  @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_procs ⇒ Object (readonly)

Returns the value of attribute at_exit_procs.



310
311
312
# File 'documented/common/script.rb', line 310

def at_exit_procs
  @at_exit_procs
end

#command_line ⇒ Object

Returns the value of attribute command_line.



311
312
313
# File 'documented/common/script.rb', line 311

def command_line
  @command_line
end

#current_label ⇒ Object

Returns the value of attribute current_label.



311
312
313
# File 'documented/common/script.rb', line 311

def current_label
  @current_label
end

#die_with ⇒ Object

Returns the value of attribute die_with.



311
312
313
# File 'documented/common/script.rb', line 311

def die_with
  @die_with
end

#downstream_buffer ⇒ Object

Returns the value of attribute downstream_buffer.



311
312
313
# File 'documented/common/script.rb', line 311

def downstream_buffer
  @downstream_buffer
end

#file_name ⇒ Object (readonly)

Returns the value of attribute file_name.



310
311
312
# File 'documented/common/script.rb', line 310

def file_name
  @file_name
end

#hidden ⇒ Object

Returns the value of attribute hidden.



311
312
313
# File 'documented/common/script.rb', line 311

def hidden
  @hidden
end

#ignore_pause ⇒ Object

Returns the value of attribute ignore_pause.



311
312
313
# File 'documented/common/script.rb', line 311

def ignore_pause
  @ignore_pause
end

#jump_label ⇒ Object

Returns the value of attribute jump_label.



311
312
313
# File 'documented/common/script.rb', line 311

def jump_label
  @jump_label
end

#kill_source ⇒ Object

Returns the value of attribute kill_source.



311
312
313
# File 'documented/common/script.rb', line 311

def kill_source
  @kill_source
end

#killed_externally ⇒ Object

Returns the value of attribute killed_externally.



311
312
313
# File 'documented/common/script.rb', line 311

def killed_externally
  @killed_externally
end

#label_order ⇒ Object (readonly)

Returns the value of attribute label_order.



310
311
312
# File 'documented/common/script.rb', line 310

def label_order
  @label_order
end

#match_stack_labels ⇒ Object

Returns the value of attribute match_stack_labels.



311
312
313
# File 'documented/common/script.rb', line 311

def match_stack_labels
  @match_stack_labels
end

#match_stack_strings ⇒ Object

Returns the value of attribute match_stack_strings.



311
312
313
# File 'documented/common/script.rb', line 311

def match_stack_strings
  @match_stack_strings
end

#name ⇒ Object (readonly)

Returns the value of attribute name.



310
311
312
# File 'documented/common/script.rb', line 310

def name
  @name
end

#no_echo ⇒ Object

Returns the value of attribute no_echo.



311
312
313
# File 'documented/common/script.rb', line 311

def no_echo
  @no_echo
end

#no_kill_all ⇒ Object

Returns the value of attribute no_kill_all.



311
312
313
# File 'documented/common/script.rb', line 311

def no_kill_all
  @no_kill_all
end

#no_pause_all ⇒ Object

Returns the value of attribute no_pause_all.



311
312
313
# File 'documented/common/script.rb', line 311

def no_pause_all
  @no_pause_all
end

#paused ⇒ Object

Returns the value of attribute paused.



311
312
313
# File 'documented/common/script.rb', line 311

def paused
  @paused
end

#quiet ⇒ Object

Returns the value of attribute quiet.



311
312
313
# File 'documented/common/script.rb', line 311

def quiet
  @quiet
end

#safe ⇒ Object (readonly)

Returns the value of attribute safe.



310
311
312
# File 'documented/common/script.rb', line 310

def safe
  @safe
end

#silent ⇒ Object

Returns the value of attribute silent.



311
312
313
# File 'documented/common/script.rb', line 311

def silent
  @silent
end

#unique_buffer ⇒ Object

Returns the value of attribute unique_buffer.



311
312
313
# File 'documented/common/script.rb', line 311

def unique_buffer
  @unique_buffer
end

#upstream_buffer ⇒ Object

Returns the value of attribute upstream_buffer.



311
312
313
# File 'documented/common/script.rb', line 311

def upstream_buffer
  @upstream_buffer
end

#vars ⇒ Object (readonly)

Returns the value of attribute vars.



310
311
312
# File 'documented/common/script.rb', line 310

def vars
  @vars
end

#want_downstream ⇒ Object

Returns the value of attribute want_downstream.



311
312
313
# File 'documented/common/script.rb', line 311

def want_downstream
  @want_downstream
end

#want_downstream_xml ⇒ Object

Returns the value of attribute want_downstream_xml.



311
312
313
# File 'documented/common/script.rb', line 311

def want_downstream_xml
  @want_downstream_xml
end

#want_script_output ⇒ Object

Returns the value of attribute want_script_output.



311
312
313
# File 'documented/common/script.rb', line 311

def want_script_output
  @want_script_output
end

#want_upstream ⇒ Object

Returns the value of attribute want_upstream.



311
312
313
# File 'documented/common/script.rb', line 311

def want_upstream
  @want_upstream
end

#watchfor ⇒ Object

Returns the value of attribute watchfor.



311
312
313
# File 'documented/common/script.rb', line 311

def watchfor
  @watchfor
end

Class Method Details

.at_exit { ... } ⇒ Boolean

Registers a block to be executed when the script exits.

Yields:

  • the block to execute on exit.

Returns:

  • (Boolean) —

    true if the block was registered successfully.



519
520
521
522
523
524
525
526
# File 'documented/common/script.rb', line 519

def self.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_procs ⇒ Boolean

Clears all exit procedures registered for the current script.

Returns:

  • (Boolean) —

    true if the procedures were cleared.



530
531
532
533
534
535
536
537
# File 'documented/common/script.rb', line 530

def self.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

.current ⇒ Script?

Returns the currently running script.

Returns:

  • (Script, nil) —

    the current script instance or nil if none is running.



366
367
368
369
370
371
372
373
# File 'documented/common/script.rb', line 366

def self.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

.db ⇒ SQLite3::Database?

Accesses the database associated with the current script.

Returns:

  • (SQLite3::Database, nil) —

    the database instance or nil if not available.



504
505
506
# File 'documented/common/script.rb', line 504

def self.db
  @@elevated_db.call
end

.distrust(_script_name) ⇒ Object



595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'documented/common/script.rb', line 595

def self.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 with the given name exists.

Parameters:

  • script_name (String) —

    the name of the script to check.

Returns:

  • (Boolean) —

    true if the script exists, false otherwise.



452
453
454
# File 'documented/common/script.rb', line 452

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

.exit! ⇒ Boolean

Forces the current script to exit immediately.

Returns:

  • (Boolean) —

    true if the script exited successfully.



541
542
543
544
545
546
547
548
# File 'documented/common/script.rb', line 541

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

.hidden ⇒ Object



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

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

.index ⇒ Object



562
563
564
# File 'documented/common/script.rb', line 562

def self.index
  Script.running
end

.kill(name) ⇒ Boolean

Kills the script with the given name.

Parameters:

  • name (String) —

    the name of the script to kill.

Returns:

  • (Boolean) —

    true if the script was killed, false otherwise.



430
431
432
433
434
435
436
437
438
439
# File 'documented/common/script.rb', line 430

def self.kill(name)
  if (s = (@@running.find { |i| i.name == name }) || (@@running.find { |i| i.name =~ /^#{name}$/i }))
    s.killed_externally = true
    s.kill_source = caller[0..2]
    s.kill
    true
  else
    false
  end
end

.list ⇒ Array<Script>

Returns a list of currently running scripts.

Returns:

  • (Array<Script>) —

    an array of running script instances.



360
361
362
# File 'documented/common/script.rb', line 360

def self.list
  @@running.dup
end

.list_trusted ⇒ Object



615
616
617
618
619
620
621
622
623
624
# File 'documented/common/script.rb', line 615

def self.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) ⇒ Boolean

Logs data related to the current script.

Parameters:

  • data (String) —

    the data to log.

Returns:

  • (Boolean) —

    true if logging was successful, false otherwise.



498
499
500
# File 'documented/common/script.rb', line 498

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

.namescript_incoming(line) ⇒ Object



574
575
576
# File 'documented/common/script.rb', line 574

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

.new_downstream(line) ⇒ Object



468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'documented/common/script.rb', line 468

def self.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) ⇒ Object



456
457
458
459
460
# File 'documented/common/script.rb', line 456

def self.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) ⇒ Object



489
490
491
492
493
# File 'documented/common/script.rb', line 489

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

.new_upstream(line) ⇒ Object



462
463
464
465
466
# File 'documented/common/script.rb', line 462

def self.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) ⇒ File?

Opens a file associated with the current script.

Parameters:

  • ext (String) —

    the file extension to use.

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

    the mode in which to open the file (default is 'r').

Returns:

  • (File, nil) —

    the opened file or nil if an error occurred.



512
513
514
# File 'documented/common/script.rb', line 512

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

.pause(name = nil) ⇒ Boolean

Pauses the currently running script or the script with the given name.

Parameters:

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

    the name of the script to pause; if nil, pauses the current script.

Returns:

  • (Boolean) —

    true if the script was paused, false otherwise.



401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'documented/common/script.rb', line 401

def self.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

Returns:

  • (Boolean)


441
442
443
444
445
446
447
# File 'documented/common/script.rb', line 441

def self.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 the specified elevated script with the given arguments.

Parameters:

  • args (Array) —

    the arguments to pass to the script.



385
386
387
388
389
# File 'documented/common/script.rb', line 385

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

.running ⇒ Object



554
555
556
557
558
559
560
# File 'documented/common/script.rb', line 554

def self.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.

Parameters:

  • name (String) —

    the name of the script to check.

Returns:

  • (Boolean) —

    true if the script is running, false otherwise.



394
395
396
# File 'documented/common/script.rb', line 394

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

.self ⇒ Object



550
551
552
# File 'documented/common/script.rb', line 550

def self.self
  Script.current
end

.start(*args) ⇒ Script

Starts a new elevated script with the given arguments.

Parameters:

  • args (Array) —

    the arguments to pass to the script.

Returns:

  • (Script) —

    the newly started script instance.



378
379
380
# File 'documented/common/script.rb', line 378

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

.trust(_script_name) ⇒ Object



579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
# File 'documented/common/script.rb', line 579

def self.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 the script with the given name.

Parameters:

  • name (String) —

    the name of the script to unpause.

Returns:

  • (Boolean) —

    true if the script was unpaused, false otherwise.



418
419
420
421
422
423
424
425
# File 'documented/common/script.rb', line 418

def self.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

Instance Method Details

#at_exit(&block) ⇒ Object



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

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

#clear ⇒ Object



839
840
841
842
843
# File 'documented/common/script.rb', line 839

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

#clear_exit_procs ⇒ Object



768
769
770
771
# File 'documented/common/script.rb', line 768

def clear_exit_procs
  @at_exit_procs.clear
  true
end

#custom? ⇒ Boolean

Returns:

  • (Boolean)


919
920
921
# File 'documented/common/script.rb', line 919

def custom?
  @custom
end

#exit ⇒ Object



773
774
775
# File 'documented/common/script.rb', line 773

def exit
  kill
end

#exit! ⇒ Object



777
778
779
780
# File 'documented/common/script.rb', line 777

def exit!
  @at_exit_procs.clear
  kill
end

#feedme_upstream ⇒ Object



905
906
907
# File 'documented/common/script.rb', line 905

def feedme_upstream
  @want_upstream = !@want_upstream
end

#get_next_label ⇒ Object



820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
# File 'documented/common/script.rb', line 820

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

#gets ⇒ Object



849
850
851
852
853
854
855
856
857
858
859
# File 'documented/common/script.rb', line 849

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? ⇒ Boolean

Returns:

  • (Boolean)


861
862
863
864
865
866
867
868
869
870
871
872
873
# File 'documented/common/script.rb', line 861

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

Returns:

  • (Boolean)


794
795
796
# File 'documented/common/script.rb', line 794

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

#instance_eval(*_a) ⇒ Object



784
# File 'documented/common/script.rb', line 784

def instance_eval(*_a);         nil; end

#instance_variable_get(*_a) ⇒ Object



782
# File 'documented/common/script.rb', line 782

def instance_variable_get(*_a); nil; end

#kill ⇒ Object



723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
# File 'documented/common/script.rb', line 723

def kill
  source = @kill_source || caller[0..2]
  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)
          unless @quiet
            if @killed_externally
              respond("--- Lich: #{@custom ? 'custom/' : ''}#{@name} was killed. (#{source.first})")
            else
              respond("--- Lich: #{@custom ? 'custom/' : ''}#{@name} has exited.")
            end
          end
          GC.start
        rescue
          respond "--- Lich: error: #{$!}"
          Lich.log "error: #{$!}\n\t#{$!.backtrace.join("\n\t")}"
        end
      end
    }
  }
  @name
end

#labels ⇒ Object



786
787
788
# File 'documented/common/script.rb', line 786

def labels
  @labels
end

#match_stack_add(label, string) ⇒ Object



909
910
911
912
# File 'documented/common/script.rb', line 909

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

#match_stack_clear ⇒ Object



914
915
916
917
# File 'documented/common/script.rb', line 914

def match_stack_clear
  @match_stack_labels.clear
  @match_stack_strings.clear
end

#pause ⇒ Object



798
799
800
801
802
803
804
805
# File 'documented/common/script.rb', line 798

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

#paused? ⇒ Boolean

Returns:

  • (Boolean)


816
817
818
# File 'documented/common/script.rb', line 816

def paused?
  @paused
end

#safe? ⇒ Boolean

Returns:

  • (Boolean)


901
902
903
# File 'documented/common/script.rb', line 901

def safe?
  @safe
end

#thread_group ⇒ Object



790
791
792
# File 'documented/common/script.rb', line 790

def thread_group
  @thread_group
end

#to_s ⇒ Object



845
846
847
# File 'documented/common/script.rb', line 845

def to_s
  @name
end

#unique_gets ⇒ Object



888
889
890
891
# File 'documented/common/script.rb', line 888

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

#unique_gets? ⇒ Boolean

Returns:

  • (Boolean)


893
894
895
896
897
898
899
# File 'documented/common/script.rb', line 893

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

#unpause ⇒ Object



807
808
809
810
811
812
813
814
# File 'documented/common/script.rb', line 807

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

#upstream_gets ⇒ Object



875
876
877
878
# File 'documented/common/script.rb', line 875

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

#upstream_gets? ⇒ Boolean

Returns:

  • (Boolean)


880
881
882
883
884
885
886
# File 'documented/common/script.rb', line 880

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