Ruby Skript asm.rb

Einerseits möchte man in Ruby wiederverwendbaren Code schreiben, lagert also Klassen in extra Dateien aus, die man dann als Bibliothek nachlädt. Andererseits ist es oft nervig und hinderlich, wenn man mehrere Dateien installieren muss, um ein kleines Skript auf einem Rechner verfügbar zu machen.

Ich hab mir deshalb ein Skript geschrieben, welches alle Quellcode-Dateien zu einer Programmdatei zusammenfügt, die man dann ideal verteilen kann.

Das Konzept: Bei der Entwicklung und beim Test werden wie bisher alle selbstgeschriebenen Klassenbibliotheken mit require in das Anwendungs-Skript geholt. Jedoch werden alle require-Zeilen, die später in eine Datei zusammengefasst werden sollen, mit einem #include markiert.

 
require 'csv'
require 'myclasslib/dbg'         #include
require 'myclasslib/string_ext'  #include

# Skriptcode...

Wenn das Skript nun freigegeben ist und verteilt werden soll, assembliert man alle Teile.

 
ruby asm.rb MeinSkript.rb > MeinSkript

Das asm.rb durchläuft MeinSkript.rb und ersetzt alle require Zeilen, die mit #include markiert sind, durch den Inhalt der Bibliotheks-Dateien. Heraus kommt eine Datei, die den ganzen Code enthält. Es werden sozusagen alle Einzelteile zusammengebaut, ähnlich wie beim statischen Linken bei C-Programmen.

Quellcode asm.rb

Der Quellcode wurde getestet unter Linux und Windows 2000 mit Ruby 1.6.8 und Ruby 1.8.2. Neben der Benutzung also unabhängiges Werkzeug, kann es auch als Bibliothek in ein eigenes Skript eingebunden werden.

Changelog

  • 2005/07/28 Shebang Ersetzung
  • 2005/02/26 Ersetzung __FILE__ durch Library-Filenamen, abschneiden alles hinter __END__, Code aufgeräumt
  • 2005/02/25 Initial Release

Patch: 2005/07/28 Shebang Ersetzung

Das bisherige Skript setzt automatisch eine Default-Shebang-Zeile ein (#!/usr/bin/env ruby). Eine kleine Erweiterung sorgt dafür, dass die Shebang Zeile des Originalskripts genommen wird, falls vorhanden. Die Methode self.insert_file muss wie folgt gepatcht werden:

 
  def self.insert_file( file, root )
    error( "File #{file} not found." ) if !File.file?(file)

    # --- BEGIN Patch
    # First line Shebang? -> replace default-Shebang
    if root
      File.foreach(file) do |firstline|
        if firstline =~ /^\#\!/
          @@out[0] = firstline.chomp
        end
        break
      end
    end
    # --- END Patch

    File.foreach(file) do |line|
  ...

Version: 2005/02/26

 
#!/usr/bin/env ruby

# Winfried Mueller, www.reintechnisch.de, Copyright, 2004, GPL
# Release 2005/02/26
# 
# asm.rb - assemble the main file and all #include requires to one file
# Use: ruby asm.rb infile > out

class Asm
  @@included = Hash.new
  @@out = Array.new
  @@header = Array.new

  def self.clear
    @@out = Array.new
    @@included = Hash.new
    @@header = Array.new
  end

  def self.out
    return @@out
  end

  def self.to_s
    @@out.join( "\n" )
  end

  # insert all required files with #include directive
  # e.g. require 'mylib/lib1'  #include
  def self.insert_file( file, root )
    error( "File #{file} not found." ) if !File.file?(file)
    File.foreach(file) do |line|

      # include all lines with '#include' at end of line
      rxp_req_include = /^\s*require\s*["'](.*?)["']\s*#\s*include/

      if line =~ rxp_req_include
        libfile = $1

        # file is already included
        if @@included[libfile] == true
          next
        end

        # include file
        @@included[libfile] = true

        out_begin_require_header( libfile )

        libfile_full = find_lib_file( libfile ) || 
                       error( "File #{libfile} not found." )

        insert_file( libfile_full, false )

        out_end_require_header( libfile )
      else
        line.chomp!

        # replace __FILE__ in included libs
        # cut all after __END__ in included libs
        if !root
          line.gsub!( /__FILE__/, "'" << File.basename( file ) << "'" )
          if line =~ /^__END__/
            return
          end
        end  

        @@out << line
      end
    end
  end

  # add a line to global header
  def self.add_header( str )
    @@header << str.chomp
  end

  # generate global header buffered in @@out
  def self.out_header
    @@out << "#!/usr/bin/env ruby"
    @@out << "#"
    @@out << "# This file is automatically generated. DO NOT MODIFY!"
    @@out << "#"
    @@out << ""
    @@header.each do |line| 
      @@out << line
    end
  end

  def self.out_begin_require_header( libfile )
    @@out << ""
    @@out << "# ========== begin include #{libfile} =========="
    @@out << "" 
  end
  private_class_method :out_begin_require_header

  def self.out_end_require_header( libfile )
    @@out << "# ========== end include #{libfile} =========="
    @@out << ""
  end
  private_class_method :out_end_require_header

  # search in all libdirs for libfile
  def self.find_lib_file( libfile )
    filename_full = nil
    $:.each do |libdir|
      f = File.join(libdir, libfile + ".rb")
      if File.file?( f )
        filename_full = f
        break
      end
    end 
    return filename_full

  end
  private_class_method :find_lib_file

  def self.error( msg )
    $stderr.puts "Error: " << msg
    raise msg
  end
  private_class_method :error
end



if __FILE__ == $0
  main_file = $*[0]
  Asm::out_header
  Asm::insert_file( main_file, true )
  puts Asm::to_s
  exit (0)
end


Version: 2005/02/25

 
#!/usr/bin/env ruby

# Winfried Mueller, www.reintechnisch.de, Copyright, 2004, GPL
# Release 2005/02/25
# 
# asm.rb - assemble the main file and all #include requires to one file
# Use: ruby asm.rb infile > out

class Asm
  @@included = Hash.new
  @@out = Array.new
  @@header = Array.new

  def self.clear
    @@out = Array.new
    @@included = Hash.new
    @@header = Array.new
  end

  def self.out
    return @@out
  end

  def self.to_s
    @@out.join( "\n" )
  end

  def self.insert_file( file )
    File.foreach(file) do |line|
      file = nil
      # include all lines with '#include' at end of line
      if line =~ /^\s*require\s*["'](.*?)["']\s*#\s*include/
        file = $1
        if @@included[file] == true
          next
        end
        @@included[file] = true
        @@out << ""
        @@out << "# ========== begin include #{$1} =========="
        @@out << "" 

        included = false
        $:.each do |libdir|
          ffile = File.join(libdir, file + ".rb")
          if File.file?( ffile )
            self.insert_file( ffile )
            included = true
            break
          end
        end
        if !included
          $stderr.puts "File #{file} not found."
          raise
        end
        @@out << "# ========== end include #{file} =========="
        @@out << ""
      else
        @@out << line.chomp
      end
    end
  end
  def self.add_header( str )
    @@header << str.chomp
  end
  def self.out_header
    @@out << "#!/usr/bin/env ruby"
    @@out << "#"
    @@out << "# This file is automatically generated. DO NOT MODIFY!"
    @@out << "#"
    @@out << ""
    @@header.each do |line| 
      @@out << line
    end
  end
end



if __FILE__ == $0
  main_file = $*[0]
  if !File.file?(main_file)
    $stderr.puts "Error: Can't find file #{main_file}"
    exit (-1)
  end
  Asm::out_header
  Asm::insert_file( main_file )
  puts Asm::to_s
  exit (0)
end