<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DZone Snippets: ruby code</title>
    <link>http://snippets.dzone.com/posts</link>
    <pubDate>Sat, 30 Aug 2008 04:36:32 GMT</pubDate>
    <description>DZone Snippets: ruby code</description>
    <item>
      <title>Open an arbitrary number of resources safely in ruby</title>
      <link>http://snippets.dzone.com/posts/show/5420</link>
      <description>I'm too lazy to work out what happens if I try &lt;code&gt;filenames.map {|f| File.open(f) }&lt;/code&gt; and the thirteenth file doesnt exist, but I bet I don't like it.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;module Enumerable&lt;br /&gt;  # Example:&lt;br /&gt;  # ['a','b'].with_files {|f,g| ... }&lt;br /&gt;  # is the same as&lt;br /&gt;  # File.open('a') {|f| File.open('b') {|g| ... } }&lt;br /&gt;  # You can specify modes with&lt;br /&gt;  # [['a', 'rb'], ['b', 'w']].with_files ...&lt;br /&gt;  def with_files(&lt;br /&gt;      meth = File.method(:open),&lt;br /&gt;      offset=0,&lt;br /&gt;      inplace=false,&lt;br /&gt;      &amp;block&lt;br /&gt;  )&lt;br /&gt;    if inplace then&lt;br /&gt;      if offset &gt;= length then&lt;br /&gt;        yield self&lt;br /&gt;      else&lt;br /&gt;        fname,mode = *self[offset]&lt;br /&gt;        File.open(fname,mode) {|f| &lt;br /&gt;          self[offset] = f&lt;br /&gt;          self.with_files(meth,offset+1,true,&amp;block)&lt;br /&gt;        }&lt;br /&gt;      end&lt;br /&gt;    else&lt;br /&gt;      dup.with_files(meth,offset,true,&amp;block)&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;/code&gt;</description>
      <pubDate>Tue, 22 Apr 2008 00:44:04 GMT</pubDate>
      <guid>http://snippets.dzone.com/posts/show/5420</guid>
      <author>tunah (Sam McCall)</author>
    </item>
    <item>
      <title>procalyzer - analyze proc frequency from WoW combat log</title>
      <link>http://snippets.dzone.com/posts/show/5322</link>
      <description>&lt;code&gt;# Usage:&lt;br /&gt;# File.open('Logs/WowCombatLog.txt') {|log| &lt;br /&gt;#    Procalyzer::procalyze(log,"Light's Ward",&amp;Procalyzer::PLAYER_SWING_OR_SPELL).dump&lt;br /&gt;# }&lt;br /&gt;&lt;br /&gt;require 'time'&lt;br /&gt;require 'csv'&lt;br /&gt;&lt;br /&gt;Stats = Struct.new(:min,:max,:n,:sx,:sx2)&lt;br /&gt;&lt;br /&gt;# calculate mean, stdev, etc for an array of numbers&lt;br /&gt;class Stats &lt;br /&gt;  def initialize(population)&lt;br /&gt;    self.n = 0&lt;br /&gt;    self.sx = self.sx2 = 0.0&lt;br /&gt;    population.each { |x|&lt;br /&gt;      self.n += 1&lt;br /&gt;      self.sx += x&lt;br /&gt;      self.sx2 += x*x;&lt;br /&gt;      self.min = x if self.min.nil? or x &lt; self.min&lt;br /&gt;      self.max = x if self.max.nil? or x &gt; self.max&lt;br /&gt;    }&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def mean&lt;br /&gt;    sx/n&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  def variance&lt;br /&gt;    sx2/n - (sx/n)**2&lt;br /&gt;  end&lt;br /&gt; &lt;br /&gt;  def stdev&lt;br /&gt;    variance ** 0.5&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def to_s&lt;br /&gt;    "#{mean} (dev: #{stdev}, range: #{min} - #{max}, n: #{n})"&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;module Procalyzer&lt;br /&gt;	# wow 2.4 log entry&lt;br /&gt;	LogEntry = Struct.new(:time,:message,:actor_guid,:actor_name,:actor_id,:target_guid,:target_name,:target_id,:rest)&lt;br /&gt;&lt;br /&gt;	# parse a log into a list of LogEntries.&lt;br /&gt;	# if block given, yields each entry and returns a list of the result&lt;br /&gt;	# else resturns a list of entries&lt;br /&gt;	def self.parse_log(stream) &lt;br /&gt;		lines = []&lt;br /&gt;		while line=stream.gets&lt;br /&gt;			entry = LogEntry.new&lt;br /&gt;			line =~ %r{^(\d+)/(\d+) (\d\d):(\d\d):(\d\d).(\d\d\d) (.*)$} or raise "Couldnt parse line: #{line}"&lt;br /&gt;			entry.time = Time.local(Time.new.year, $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, 1000*$6.to_i)&lt;br /&gt;			entry.message, &lt;br /&gt;				entry.actor_guid, entry.actor_name, entry.actor_id, &lt;br /&gt;				entry.target_guid, entry.target_name, entry.target_id, &lt;br /&gt;				*rest = *CSV.parse_line($7.strip)&lt;br /&gt;			entry.rest = rest&lt;br /&gt;			if block_given?&lt;br /&gt;				lines &lt;&lt; yield(entry)&lt;br /&gt;			else&lt;br /&gt;				lines &lt;&lt; entry&lt;br /&gt;			end&lt;br /&gt;		end&lt;br /&gt;		lines&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	# Scans a logfile, runs event handlers for events matching certain filters&lt;br /&gt;	class EventScanner&lt;br /&gt;		def initialize&lt;br /&gt;			@filters = []&lt;br /&gt;			@start = []&lt;br /&gt;			@end = []&lt;br /&gt;		end&lt;br /&gt;	&lt;br /&gt;		# add a handler to be run for events matching filter&lt;br /&gt;		def on(filter, &amp;block)&lt;br /&gt;			@filters &lt;&lt; [filter,block]&lt;br /&gt;		end&lt;br /&gt;		# add a handler at the start&lt;br /&gt;		def start(&amp;block)&lt;br /&gt;			@start &lt;&lt; block&lt;br /&gt;		end&lt;br /&gt;		# add a handler at the end&lt;br /&gt;		def end(&amp;block)&lt;br /&gt;			@end &lt;&lt; block&lt;br /&gt;		end&lt;br /&gt;	&lt;br /&gt;		# scan the stream and run handlers&lt;br /&gt;		def run(file) &lt;br /&gt;			@start.each {|s| s.call() }&lt;br /&gt;			Procalyzer::parse_log(file) {|entry|&lt;br /&gt;				@filters.each {|filter,block|&lt;br /&gt;					block.call(entry) if filter[entry]&lt;br /&gt;				}&lt;br /&gt;			}&lt;br /&gt;			@end.each {|s| s.call() }&lt;br /&gt;		end&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	# Container for analysis results&lt;br /&gt;	# durations is a list of the proc lengths found&lt;br /&gt;	# intervals is the list of times from one proc to the next&lt;br /&gt;	# cooldown is the estimated or specified internal cooldown&lt;br /&gt;	# procs is a list of 1 or 0 entries specifying whether each eligible event procced the aura&lt;br /&gt;	ProcResults = Struct.new(:durations, :intervals, :cooldown, :procs)&lt;br /&gt;	class ProcResults&lt;br /&gt;		def initialize&lt;br /&gt;			self.durations=[]&lt;br /&gt;			self.intervals=[]&lt;br /&gt;			self.procs=[]&lt;br /&gt;		end&lt;br /&gt;		# print results in a pretty human readable form&lt;br /&gt;		def dump(out=$stdout)&lt;br /&gt;			if self.durations.empty?&lt;br /&gt;				out.puts "Proc did not occur."&lt;br /&gt;			elsif self.intervals.empty?&lt;br /&gt;				out.puts "Proc occurred once only."&lt;br /&gt;			else&lt;br /&gt;				dur_stats = Stats.new(durations)&lt;br /&gt;				out.puts "Duration: #{dur_stats}"&lt;br /&gt;				if dur_stats.stdev/dur_stats.mean &gt; 0.1&lt;br /&gt;					out.puts "Standard deviation of duration is more than 10%."&lt;br /&gt;					out.puts "This can indicate a proc with no cooldown that refreshes itself."&lt;br /&gt;				end&lt;br /&gt;				interval_stats = Stats.new(intervals)&lt;br /&gt;				out.puts "Interval: #{interval_stats}"&lt;br /&gt;				out.printf("Uptime: %.2f%%\n", 100.0*(dur_stats.sx - durations[-1])/interval_stats.sx)&lt;br /&gt;				out.puts "Internal cooldown: #{cooldown}"&lt;br /&gt;				proc_stats = Stats.new(procs)			&lt;br /&gt;				out.printf("Proc chance: %.2f%% (%d/%d)\n", 100*proc_stats.mean, proc_stats.sx.to_i, proc_stats.n)&lt;br /&gt;			end&lt;br /&gt;		end&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	# Analyse a log file, looking for a self-buff triggered by an event.&lt;br /&gt;	# Assume it has a fixed % to proc unless it is on cooldown (or already active, &lt;br /&gt;	# in which case it could theoretically proc but won't slow in the log)&lt;br /&gt;	# we do this in two phases:&lt;br /&gt;	# 1) measure proc durations and intervals, and estimate cooldown if it's not specified&lt;br /&gt;	#    (just minimum interval rounded down to nearest second)&lt;br /&gt;	# 2) count eligible proccing events (ones outside cooldown) and see which procced it&lt;br /&gt;	# file is a seekable stream containing the log&lt;br /&gt;	# auraname is the buff name like "Haste"&lt;br /&gt;	# cooldown is the internal cooldown if known (else will be estimated)&lt;br /&gt;	# proc_trigger is a filter block for proc events (takes an event, returns true if the event can proc&lt;br /&gt;	#   the aura, assuming the aura isnt on cooldown or already up)&lt;br /&gt;	# returns a procresults object&lt;br /&gt;	def self.procalyze(file, auraname, cooldown=nil, &amp;proc_trigger) &lt;br /&gt;		results = ProcResults.new&lt;br /&gt;		results.cooldown=cooldown&lt;br /&gt;&lt;br /&gt;		scanner = EventScanner.new&lt;br /&gt;	&lt;br /&gt;		start = nil&lt;br /&gt;		last = nil&lt;br /&gt;	&lt;br /&gt;		# Aura start: record start time and interval&lt;br /&gt;		scanner.on(proc {|e| e.message == "SPELL_AURA_APPLIED" and e.rest[1] == auraname} ) {|evt|&lt;br /&gt;			start = evt.time.to_f&lt;br /&gt;			results.intervals &lt;&lt; (start-last) if last&lt;br /&gt;			last = start&lt;br /&gt;		}&lt;br /&gt;		# Aura finish: record duration&lt;br /&gt;		scanner.on(proc {|e| e.message == "SPELL_AURA_REMOVED" and e.rest[1] == auraname} ) {|evt|&lt;br /&gt;			if start&lt;br /&gt;				duration = evt.time.to_f - start&lt;br /&gt;				results.durations &lt;&lt; duration&lt;br /&gt;			end&lt;br /&gt;		}&lt;br /&gt;		scanner.run(file)&lt;br /&gt;&lt;br /&gt;		return results if results.intervals.length==0&lt;br /&gt;&lt;br /&gt;		# Estimate cooldown if not given	&lt;br /&gt;		if results.cooldown.nil?&lt;br /&gt;			interval_stats = Stats.new(results.intervals)&lt;br /&gt;			duration_stats = Stats.new(results.durations)&lt;br /&gt;			if(duration_stats.stdev/duration_stats.mean &gt; 0.1)&lt;br /&gt;				results.cooldown = 0.0&lt;br /&gt;			else&lt;br /&gt;				results.cooldown = interval_stats.min.floor&lt;br /&gt;			end&lt;br /&gt;			puts "Using cooldown = #{results.cooldown}" if $DEBUG&lt;br /&gt;		end&lt;br /&gt;	&lt;br /&gt;		file.pos = 0 # restart file&lt;br /&gt;		last_swing_time = nil&lt;br /&gt;		last_swing_could_proc = false&lt;br /&gt;		aura_up = false&lt;br /&gt;		start = nil&lt;br /&gt;		scanner = EventScanner.new&lt;br /&gt;&lt;br /&gt;		start_swing = proc{|evt| last_swing_time = evt.time.to_f; last_swing_count_proc = true  }&lt;br /&gt;		end_swing = proc{&lt;br /&gt;			if(last_swing_could_proc)&lt;br /&gt;				puts "Possible proc: #{last_swing_time}" if $DEBUG&lt;br /&gt;				results.procs &lt;&lt; (aura_up ? 1 : 0)&lt;br /&gt;			end&lt;br /&gt;			last_swing_could_proc = false&lt;br /&gt;			last_swing_time = nil&lt;br /&gt;		}&lt;br /&gt;&lt;br /&gt;		# Aura start, start cooldown timer and set aura_up&lt;br /&gt;		scanner.on(proc {|e| e.message == "SPELL_AURA_APPLIED" and e.rest[1] == auraname} ) {|evt|&lt;br /&gt;			puts "Aura start: #{evt.time.to_f}" if $DEBUG&lt;br /&gt;			start = evt.time.to_f&lt;br /&gt;			aura_up = true&lt;br /&gt;			end_swing.call()&lt;br /&gt;		}&lt;br /&gt;		# Aura end, clear aura_up&lt;br /&gt;		scanner.on(proc {|e| e.message == "SPELL_AURA_REMOVED" and e.rest[1] == auraname} ) {|evt|&lt;br /&gt;			aura_up = false&lt;br /&gt;		}&lt;br /&gt;		# Proc trigger event - eg a melee swing&lt;br /&gt;		scanner.on(proc_trigger) {|evt|&lt;br /&gt;			# first, this is the point where we confirm the previous swing didnt proc the aura&lt;br /&gt;			end_swing.call()&lt;br /&gt;&lt;br /&gt;			# record details of this swing, incl whether it could proc&lt;br /&gt;			time_since_last = (start.nil?) ? 99999.0 : (evt.time.to_f - start)&lt;br /&gt;			last_swing_could_proc = (not aura_up) &amp;&amp; (time_since_last &gt; results.cooldown)&lt;br /&gt;			last_swing_time = evt.time.to_f&lt;br /&gt;		}&lt;br /&gt;		# at the end of the log file, confirm that the last swing didnt proc the aura&lt;br /&gt;		scanner.end(&amp;end_swing) &lt;br /&gt;&lt;br /&gt;		scanner.run(file)&lt;br /&gt;		results&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	# actor_id for the player&lt;br /&gt;	PLAYER_ID = "0x511"&lt;br /&gt;&lt;br /&gt;	# predefined filters for common procs&lt;br /&gt;	PLAYER_SWING = proc{|evt| evt.actor_id == PLAYER_ID and evt.message="SWING_DAMAGE" }&lt;br /&gt;	PLAYER_SWING_OR_SPELL = proc{|evt| evt.actor_id == PLAYER_ID and (evt.message=="SWING_DAMAGE" or evt.message=="SPELL_DAMAGE") }&lt;br /&gt;	PLAYER_SPELL_DAMAGE = proc{|evt| evt.actor_id == PLAYER_ID and evt.message=="SPELL_DAMAGE" }&lt;br /&gt;        PLAYER_RANGED_OR_SPELL = proc{|evt| evt.actor_id == PLAYER_ID and (evt.message=="RANGE_DAMAGE" or evt.message=="SPELL_DAMAGE") }&lt;br /&gt;end&lt;/code&gt;</description>
      <pubDate>Fri, 04 Apr 2008 01:45:56 GMT</pubDate>
      <guid>http://snippets.dzone.com/posts/show/5322</guid>
      <author>tunah (Sam McCall)</author>
    </item>
    <item>
      <title>Ace2 mod manager</title>
      <link>http://snippets.dzone.com/posts/show/4136</link>
      <description>&lt;code&gt;&lt;br /&gt;#!/usr/local/bin/ruby&lt;br /&gt;&lt;br /&gt;require 'open-uri'&lt;br /&gt;require 'fileutils'&lt;br /&gt;&lt;br /&gt;# should be placed inside WoW directory&lt;br /&gt;# usage:&lt;br /&gt;# ace_updater                  - updates all installed mods&lt;br /&gt;# ace_updater update           - updates all installed mods&lt;br /&gt;# ace_updater update Omen Grid - updates selected mods&lt;br /&gt;# ace_updater add Threat-1.0   - install a new mod&lt;br /&gt;# ace_updater delete FuBar     - uninstall an existing mod&lt;br /&gt;# add the -v flag to an update to show changes from the changelog.&lt;br /&gt;&lt;br /&gt;# You may need to adjust zip_extract for your system.&lt;br /&gt;# file is the zip file to be extracted&lt;br /&gt;# target_dir is the existing directory to extract into.&lt;br /&gt;&lt;br /&gt;class Updater&lt;br /&gt;	# requirers some OS customisation, this WFM on Mac OS X&lt;br /&gt;	def zip_extract(file,target_dir)&lt;br /&gt;		system(&lt;br /&gt;			"unzip", "-q", file, "-d", target_dir&lt;br /&gt;		) or raise "Error extracting (#{$?}): unzip #{file.inspect} -d #{target_dir.inspect}"&lt;br /&gt;	end	&lt;br /&gt;&lt;br /&gt;	def initialize(wow_path,opts,listing_url="http://files.wowace.com/")&lt;br /&gt;		@opts=opts&lt;br /&gt;		@wow_path = wow_path&lt;br /&gt;		@addons_dir = File.join(@wow_path,"Interface","Addons")&lt;br /&gt;		@addons_download_dir = File.join(@wow_path,"Interface","ace_updater")&lt;br /&gt;		@listing_url=listing_url&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def mod_index&lt;br /&gt;		@mod_index ||= begin&lt;br /&gt;			puts "Fetching Ace mod index from #{@listing_url}..."&lt;br /&gt;			m={}&lt;br /&gt;			open(@listing_url) {|f| &lt;br /&gt;				f.read &lt;br /&gt;			}.scan(&lt;br /&gt;				%r{&lt;td&gt;\s*&lt;a href="([^"]+)"&gt;([^&gt;]+)&lt;/a&gt;\s*&lt;/td&gt;\s*&lt;td&gt;r(\d+)&lt;/td&gt;}&lt;br /&gt;			) {|url,mod,revision|&lt;br /&gt;				m[mod] = [revision.to_i,url]&lt;br /&gt;			} &lt;br /&gt;			puts "#{m.length} entries found."&lt;br /&gt;			puts&lt;br /&gt;			m&lt;br /&gt;		end&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def each_installed_mod&lt;br /&gt;		Dir.foreach(@addons_dir) {|addon|&lt;br /&gt;			next if ["..","."].include? addon &lt;br /&gt;			yield addon,installed_revision(addon)&lt;br /&gt;		}&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def installed_revision(addon)&lt;br /&gt;		addon_dir = File.join(@addons_dir,addon)&lt;br /&gt;		x = Dir.glob(File.join(addon_dir,"Changelog-#{addon}-r*.xml"))&lt;br /&gt;		return nil if x.empty?&lt;br /&gt;		x[0] =~ /Changelog-#{addon}-r(\d+)\.xml/&lt;br /&gt;		$1.to_i&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def get_changes(changelog,local_revision) &lt;br /&gt;		changes=[]&lt;br /&gt;		current_rev = "?????"&lt;br /&gt;		reading_changes = false&lt;br /&gt;		File.open(changelog) {|f|&lt;br /&gt;			f.read&lt;br /&gt;		}.each_line{|l|&lt;br /&gt;			l.strip!&lt;br /&gt;			if l =~ /^r(\d+) \|/&lt;br /&gt;				current_rev = $1.to_i&lt;br /&gt;				break if current_rev &lt;= local_revision&lt;br /&gt;			elsif l=~/^-+$/&lt;br /&gt;				reading_changes = false&lt;br /&gt;			elsif l=~/^$/&lt;br /&gt;				reading_changes = true&lt;br /&gt;			else&lt;br /&gt;				changes &lt;&lt; [current_rev,l] if reading_changes&lt;br /&gt;			end&lt;br /&gt;		}&lt;br /&gt;		changes.stable_sort_by {|r,t| r}&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def update_mods(list=nil)&lt;br /&gt;		list=nil if list.empty?&lt;br /&gt;&lt;br /&gt;		remote_mods = mod_index()&lt;br /&gt;&lt;br /&gt;		puts "Scanning #{list ? 'chosen' : 'all'} mods for updates..."&lt;br /&gt;		jobs = []&lt;br /&gt;		each_installed_mod {|mod,revision|&lt;br /&gt;			next if revision.nil? #non-ace&lt;br /&gt;			next if list and not list.include?(mod) #not on the list&lt;br /&gt;&lt;br /&gt;			remote_revision,url = remote_mods[mod]&lt;br /&gt;			if remote_revision.nil? or remote_revision &lt; revision&lt;br /&gt;				nonfatal("#{mod}: local is newer than remote (r#{revision.inspect} vs r#{remote_revision.inspect}). Skipping.")&lt;br /&gt;			elsif remote_revision &gt; revision&lt;br /&gt;				jobs &lt;&lt; [mod,revision,remote_revision,url]&lt;br /&gt;			end&lt;br /&gt;&lt;br /&gt;			list.delete mod if list&lt;br /&gt;		}&lt;br /&gt;		list.each {|x|&lt;br /&gt;			$stderr.puts "WARNING: #{x} is not an installed Ace addon, skipping update."&lt;br /&gt;		} if list&lt;br /&gt;&lt;br /&gt;		if jobs.length == 0&lt;br /&gt;			puts "#{list ? 'Chosen' : 'All'} mods up to date."&lt;br /&gt;		else&lt;br /&gt;			install_mods(jobs)&lt;br /&gt;		end&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def add_mods(modlist)&lt;br /&gt;		remote_mods = mod_index()&lt;br /&gt;		jobs = modlist.map {|mod|&lt;br /&gt;			if remote_mods.key?(mod)&lt;br /&gt;				rev,url = remote_mods[mod]&lt;br /&gt;				[mod,nil,rev,url]&lt;br /&gt;			else&lt;br /&gt;				$stderr.puts "WARNING: #{mod} not found in mod index, skipping"&lt;br /&gt;			end&lt;br /&gt;		}.compact&lt;br /&gt;		install_mods(jobs)&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def delete_mods(modlist)&lt;br /&gt;		modlist = modlist.map {|x|&lt;br /&gt;			next if ['.','..'].include?(x) # this would be lame&lt;br /&gt;			rev = installed_revision(x)&lt;br /&gt;			if rev&lt;br /&gt;				[x,rev]&lt;br /&gt;			else&lt;br /&gt;				nonfatal("#{x} is not an installed Ace addon, skipping")&lt;br /&gt;			end&lt;br /&gt;		}.compact&lt;br /&gt;&lt;br /&gt;		modlist.each_with_index {|(mod,rev),i|&lt;br /&gt;			status(i,modlist.length,"delete",mod,rev)&lt;br /&gt;			FileUtils.remove_dir(File.join(@addons_dir,mod))&lt;br /&gt;		}&lt;br /&gt;	end&lt;br /&gt;	&lt;br /&gt;	def install_mods(jobs)&lt;br /&gt;		FileUtils.remove_dir(@addons_download_dir) if File.directory?(@addons_download_dir)&lt;br /&gt;		Dir.mkdir(@addons_download_dir) &lt;br /&gt;		jobs.each_with_index{|(mod,revision,remote_revision,url),index|&lt;br /&gt;			status(index, jobs.length, (revision ? 'update' : 'add'), mod, *([revision,remote_revision].compact))&lt;br /&gt;&lt;br /&gt;			zipfile = File.join(@addons_download_dir,"#{mod}.zip")&lt;br /&gt;&lt;br /&gt;			File.open(zipfile,'w') {|f|&lt;br /&gt;				open(url) {|data|&lt;br /&gt;					FileUtils.copy_stream(data,f)&lt;br /&gt;				}&lt;br /&gt;			}&lt;br /&gt;&lt;br /&gt;			zip_extract(zipfile,@addons_download_dir)&lt;br /&gt;			expected_output_dir = File.join(@addons_download_dir,mod)&lt;br /&gt;			destination_dir = File.join(@addons_dir,mod)&lt;br /&gt;			backup_dir = "#{destination_dir}.backup"&lt;br /&gt;&lt;br /&gt;			unless File.directory?(expected_output_dir)&lt;br /&gt;				nonfatal("#{expected_output_dir} not created as expected")&lt;br /&gt;				nonfatal("Check #{zipfile}")&lt;br /&gt;				nonfatal("Skipping installation of #{mod}")&lt;br /&gt;			end&lt;br /&gt;&lt;br /&gt;			changelog = File.join(expected_output_dir,"Changelog-#{mod}-r#{remote_revision}.xml")&lt;br /&gt;			# for some reason these often get wrong privs&lt;br /&gt;			FileUtils.chmod 0644,changelog&lt;br /&gt;			if revision and @opts.include?('-v') # show changelog&lt;br /&gt;				changes = get_changes(changelog,revision)&lt;br /&gt;				changes.each {|revision,text|&lt;br /&gt;					puts("   * [r%d] %s" % [revision,text])&lt;br /&gt;				}&lt;br /&gt;			end&lt;br /&gt;&lt;br /&gt;			FileUtils.remove(zipfile)&lt;br /&gt;			FileUtils.mv(destination_dir,backup_dir) if revision&lt;br /&gt;			FileUtils.mv(expected_output_dir,destination_dir)&lt;br /&gt;			FileUtils.remove_dir(backup_dir) if revision&lt;br /&gt;		}&lt;br /&gt;		FileUtils.remove_dir(@addons_download_dir)&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def status(sequence,total,action,mod,rev1,rev2=nil)&lt;br /&gt;		rev2 = " -&gt; r#{rev2}" if rev2&lt;br /&gt;		puts("[%2d/%2d] %6s %16s r%d%s" % [sequence+1,total,action,mod,rev1,rev2])&lt;br /&gt;	end&lt;br /&gt;&lt;br /&gt;	def nonfatal(s)&lt;br /&gt;		$stderr.puts("WARNING: #{s}")&lt;br /&gt;	end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;class Array&lt;br /&gt;  def stable_sort_by&lt;br /&gt;    n = 0&lt;br /&gt;    sort_by {|x| n+= 1; [yield(x), n]}&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if __FILE__ == $0&lt;br /&gt;	opts,args = ARGV.partition {|text| text[0] == ?- }&lt;br /&gt;	args &lt;&lt; 'update' if args.empty?&lt;br /&gt;	command = args.shift.downcase&lt;br /&gt;&lt;br /&gt;	updater = Updater.new(File.dirname(__FILE__),opts)&lt;br /&gt;&lt;br /&gt;	case command&lt;br /&gt;	when 'update'&lt;br /&gt;		updater.update_mods(args)&lt;br /&gt;	when 'add'&lt;br /&gt;		raise "Must specify mods to add" if args.empty?&lt;br /&gt;		updater.add_mods(args)&lt;br /&gt;	when 'delete'&lt;br /&gt;		raise "Must specify mods to delete" if args.empty?&lt;br /&gt;		updater.delete_mods(args)&lt;br /&gt;	else&lt;br /&gt;		puts "Valid commands: update add delete"&lt;br /&gt;	end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;</description>
      <pubDate>Wed, 13 Jun 2007 10:51:27 GMT</pubDate>
      <guid>http://snippets.dzone.com/posts/show/4136</guid>
      <author>tunah (Sam McCall)</author>
    </item>
    <item>
      <title>WoW auction house search library</title>
      <link>http://snippets.dzone.com/posts/show/3811</link>
      <description>Library to search WoW auction houses using the www.auctionwowhouse.com site's web service&lt;br /&gt;Example usage;&lt;br /&gt;eldre = Warcraft::AuctionHouse.new("Eldre'Thalas","Alliance")&lt;br /&gt;gems = eldre.query("solid star of elune", :sort =&gt; 'bid', :order =&gt; 'asc')&lt;br /&gt;puts gems[0] unless gems.empty?&lt;br /&gt;&lt;br /&gt;[Solid Star of Elune]: 65g/70g (14:15 left)&lt;br /&gt;&lt;code&gt;&lt;br /&gt;require 'net/http'&lt;br /&gt;require 'delegate'&lt;br /&gt;&lt;br /&gt;module Warcraft&lt;br /&gt;  # Stores details of an auction&lt;br /&gt;  Auction = Struct.new(:name,:quality,:quantity,:seller,:bid,:buyout,:time) unless Auction&lt;br /&gt;  class Auction&lt;br /&gt;    def to_s&lt;br /&gt;      times = if quantity &gt; 1 then "x#{quantity}" end&lt;br /&gt;      bo = if buyout then "/#{buyout}" end&lt;br /&gt;      "[#{name}]#{times}: #{bid}#{bo} (#{time} left)"&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;  CategoryInfo = &lt;br /&gt;&lt;&lt;-EOF.strip&lt;br /&gt;weapon&lt;br /&gt; 1h axe&lt;br /&gt; 2h axe&lt;br /&gt; bow&lt;br /&gt; gun&lt;br /&gt; 1h mace&lt;br /&gt; 2h mace&lt;br /&gt; polearm&lt;br /&gt; 1h sword&lt;br /&gt; 2h sword&lt;br /&gt; staff&lt;br /&gt; fist&lt;br /&gt; misc&lt;br /&gt; dagger&lt;br /&gt; thrown&lt;br /&gt; crossbow&lt;br /&gt; wand&lt;br /&gt; fishing pole&lt;br /&gt;armor&lt;br /&gt; miscellaneous&lt;br /&gt; cloth&lt;br /&gt; leather&lt;br /&gt; mail&lt;br /&gt; plate&lt;br /&gt; shield&lt;br /&gt; libram&lt;br /&gt; idol&lt;br /&gt; totem&lt;br /&gt;container&lt;br /&gt; bag&lt;br /&gt; soul bag&lt;br /&gt; herb bag&lt;br /&gt; enchanting bag&lt;br /&gt; engineering bag&lt;br /&gt; gem bag&lt;br /&gt; mining bag&lt;br /&gt;consumable&lt;br /&gt;trade good&lt;br /&gt;projectile&lt;br /&gt; arrow&lt;br /&gt; bullet&lt;br /&gt;quiver&lt;br /&gt; quiver&lt;br /&gt; ammo pouch&lt;br /&gt;recipe&lt;br /&gt; book&lt;br /&gt; leather&lt;br /&gt; tailor&lt;br /&gt; engineering&lt;br /&gt; blacksmithing&lt;br /&gt; cooking&lt;br /&gt; alchemy&lt;br /&gt; first aid&lt;br /&gt; enchanting&lt;br /&gt; jewelcrafting&lt;br /&gt;reagent&lt;br /&gt;misc&lt;br /&gt;EOF&lt;br /&gt;  ItemClasses=Hash.new {|h,k| raise "Unknown class #{k}"}&lt;br /&gt;  ItemSubclasses=Hash.new {|h,k| raise "Unknown subclass #{k}"}&lt;br /&gt;  ItemHierarchy = [ItemClasses,ItemSubclasses]&lt;br /&gt;  ItemClasses[nil]=ItemSubclasses[nil]=nil&lt;br /&gt;  CategoryInfo.each_with_index {|line,i|&lt;br /&gt;    line =~ /^( *)(\w.*?)(\s*)$/&lt;br /&gt;    ItemHierarchy[$1.length][$2]=i+1&lt;br /&gt;  }&lt;br /&gt;  Qualities = {&lt;br /&gt;    "common" =&gt; 1,&lt;br /&gt;    "white" =&gt; 1,&lt;br /&gt;    "uncommon" =&gt; 2,&lt;br /&gt;    "green" =&gt; 2,&lt;br /&gt;    "rare" =&gt; 3,&lt;br /&gt;    "blue" =&gt; 3,&lt;br /&gt;    "epic" =&gt; 4,&lt;br /&gt;    "purple" =&gt; 4&lt;br /&gt;  }&lt;br /&gt;  Sorting = {&lt;br /&gt;    "name" =&gt; 6,&lt;br /&gt;    "level" =&gt; 2,&lt;br /&gt;    "time" =&gt; 3,&lt;br /&gt;    "seller" =&gt; 7,&lt;br /&gt;    "bid" =&gt; 4,&lt;br /&gt;    "price" =&gt; 4&lt;br /&gt;  }&lt;br /&gt;  Order = {&lt;br /&gt;    "ascending" =&gt; 0,&lt;br /&gt;    "asc" =&gt; 0,&lt;br /&gt;    "desc" =&gt; 1,&lt;br /&gt;    "descending" =&gt; 1&lt;br /&gt;  }&lt;br /&gt;  &lt;br /&gt;  # Represents an AH to query. &lt;br /&gt;  class AuctionHouse&lt;br /&gt;    # e.g. AuctionHouse.new("Eldre'Thalas","Alliance")&lt;br /&gt;    # Neutral AH are not available&lt;br /&gt;    # Third parameter is 'EU' if you're on a european server.&lt;br /&gt;    def initialize(realm,faction,locale="US")&lt;br /&gt;      @realm,@locale = realm,locale&lt;br /&gt;      @faction = case faction.to_s.downcase&lt;br /&gt;      when "alliance"&lt;br /&gt;        "Alliance"&lt;br /&gt;      when "horde"&lt;br /&gt;        "Horde"&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    &lt;br /&gt;    # Main entry point.&lt;br /&gt;    # Usage: query("item name", :opt1 =&gt; "value", ...)&lt;br /&gt;    # Item name can be nil. &lt;br /&gt;    # Options are:&lt;br /&gt;    #  :min     minimum level item&lt;br /&gt;    #  :max     maximum level item&lt;br /&gt;    #  :quality white/green/blue/purple minimum item quality&lt;br /&gt;    #  :type    weapon/armor/consumable/trade good/recipe etc&lt;br /&gt;    #  :subtype 1h axe/enchanting/soul bag etc - must have the correct type specified too&lt;br /&gt;    #  :seller  seller to search for&lt;br /&gt;    #  :sort    name/level/time/bid/seller sorting method&lt;br /&gt;    #  :order   asc/desc sort order&lt;br /&gt;    #  :page    which page of results (1-based). 10 returned at a time.&lt;br /&gt;    # Returns an array of Warcraft::Auctions.&lt;br /&gt;    def query(name, opts={})&lt;br /&gt;      params = {}&lt;br /&gt;      params["realm"] = "#@realm #@locale" #"Eldre'Thalas US"&lt;br /&gt;      params["faction"] = if @faction=="Alliance" then 1 else 2 end&lt;br /&gt;      params["ItemName"] = name&lt;br /&gt;      params["LevelStart"] = opts[:min] # Level range to filter by&lt;br /&gt;      params["LevelEnd"] = opts[:max]&lt;br /&gt;      params["Seller"] = opts[:seller]&lt;br /&gt;      params["Rarity"] = Qualities[opts[:quality]]&lt;br /&gt;      params["itemClassID"] = ItemClasses[opts[:type]] &lt;br /&gt;      params["itemSubClass"] = ItemSubclasses[opts[:subtype]] &lt;br /&gt;      params["pagenum"] = (opts[:page] || 1).to_s&lt;br /&gt;      params["invenTypeID"] = nil # think this is the slot for armor searches - e.g. armor -&gt; leather -&gt; *shoulder*. unimplemented.&lt;br /&gt;      params["sort_column"] = Sorting[opts[:sort]]&lt;br /&gt;      params["sort_order"] = Order[opts[:order]]&lt;br /&gt;      &lt;br /&gt;      querytext = MiniJSON::encode(params)&lt;br /&gt;      &lt;br /&gt;      puts "Query:\n#{querytext}" if $DEBUG&lt;br /&gt;      &lt;br /&gt;      retrieve_results(querytext)&lt;br /&gt;    end&lt;br /&gt;    &lt;br /&gt;    def retrieve_results(querytext)&lt;br /&gt;      # We just post the JSON to the given URL and get a JSON encoded string back&lt;br /&gt;      req = Net::HTTP::Post.new("/AuctionInfo.asmx/getAuctionInfo")&lt;br /&gt;      req["Content-Type"]="application/json"&lt;br /&gt;      response = Net::HTTP.new("www.auctionwowhouse.com",80).start {|http| http.request(req,querytext)}&lt;br /&gt;      response.error! unless Net::HTTPSuccess === response&lt;br /&gt;      resulttext = response.body&lt;br /&gt;      resulttext = MiniJSON::decode(resulttext)&lt;br /&gt;      puts "Response:\n#{resulttext}" if $DEBUG&lt;br /&gt;      &lt;br /&gt;      parse_results(resulttext)&lt;br /&gt;    end&lt;br /&gt;    &lt;br /&gt;    # Web service returns a blob of ugly html, so we scrape the data out of it. Cover your eyes...&lt;br /&gt;    def parse_results(resulttext)&lt;br /&gt;      items = []&lt;br /&gt;&lt;br /&gt;      resulttext.scan(/&lt;TR style='height.*?&gt;(.*?)&lt;\/TR&gt;/) {|text,| # A row of the main table containing an item&lt;br /&gt;        puts "Item:\n#{text}" if $DEBUG&lt;br /&gt;        item = Auction.new&lt;br /&gt;        text.scan(/&lt;font color=(.*?)&gt;(.*?)&lt;\/font&gt;/) {|color,name|&lt;br /&gt;          item.quality = color&lt;br /&gt;          item.name = name&lt;br /&gt;        }&lt;br /&gt;        text.scan(/&lt;td width="81" .*?&gt;(.*?)&lt;\/td&gt;/) {|name,|&lt;br /&gt;          item.seller = name&lt;br /&gt;        }&lt;br /&gt;        text.scan(/&lt;td width="75" .*?&gt;(.*?)&lt;\/td&gt;/) {|q,|&lt;br /&gt;          item.quantity = q.to_i&lt;br /&gt;        }&lt;br /&gt;    		text.scan(/&lt;td width="82" .*?&gt;&lt;div .*?&gt;(.*?)&lt;\/div&gt;&lt;\/td&gt;/) {|time,|&lt;br /&gt;    		  item.time = Duration.new(time.to_i)&lt;br /&gt;  		  }&lt;br /&gt;  		  text.scan(%r{(?:(\d+)&lt;img border=0 src="/images/wow_40_jin\.gif"&gt;)?(?:(\d+)&lt;img border=0 src="/images/wow_42_yin\.gif"&gt;)?(\d+)&lt;img border=0 src="/images/wow_44_tong\.gif"&gt;&lt;BR&gt;(?:(?:(\d+)&lt;img border=0 src="/images/wow_40_jin\.gif"&gt;)?(?:(\d+)&lt;img border=0 src="/images/wow_42_yin\.gif"&gt;)?(\d+)&lt;img border=0 src="/images/wow_44_tong\.gif"&gt;)?&lt;/a&gt;}) {|bg,bs,bc,g,s,c|&lt;br /&gt;          item.bid = Price.new(10000*bg.to_i + 100 * bs.to_i + bc.to_i)&lt;br /&gt;          item.buyout = Price.new(10000*g.to_i + 100 * s.to_i + c.to_i) if g #buyout may be absent&lt;br /&gt;        }&lt;br /&gt;        items &lt;&lt; item&lt;br /&gt;      }&lt;br /&gt;      &lt;br /&gt;      items&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # Chunk of money (gold/silver/copper)&lt;br /&gt;  class Price &lt; DelegateClass(Integer); end unless Price # So we can reload the library without throwing&lt;br /&gt;  class Price&lt;br /&gt;    def gold; self / 10000; end&lt;br /&gt;    def silver; (self / 100)%100; end&lt;br /&gt;    def copper; self % 100; end&lt;br /&gt;    def to_a; [gold,silver,copper]; end&lt;br /&gt;    def inspect; "&lt;#{self.class} #{self.to_i}&gt;"; end&lt;br /&gt;    &lt;br /&gt;    # to display we pick the dominant unit (gold if it's at least 1g, else silver if it's at &lt;br /&gt;    # least 1s, else copper) and round to the nearest&lt;br /&gt;    def to_s&lt;br /&gt;      big,little,letter = &lt;br /&gt;        if gold &gt; 0&lt;br /&gt;          [gold,silver,"g"]&lt;br /&gt;        elsif silver &gt; 0&lt;br /&gt;          [silver,copper,"s"]&lt;br /&gt;        else&lt;br /&gt;          [copper,0,"c"]&lt;br /&gt;        end&lt;br /&gt;      big += 1 if little &gt;= 50&lt;br /&gt;      "#{big}#{letter}" &lt;br /&gt;    end&lt;br /&gt;    &lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  # Measure of time (hours-minutes-seconds)&lt;br /&gt;  class Duration &lt; DelegateClass(Integer); end unless Duration&lt;br /&gt;  class Duration&lt;br /&gt;    def hours; self / 3600; end&lt;br /&gt;    def minutes; (self / 60)%60; end&lt;br /&gt;    def seconds; self % 60; end&lt;br /&gt;    def to_a; [hours,minutes,seconds] end&lt;br /&gt;    def to_s; "%d:%02d" % self.to_a; end # just show hours and minutes&lt;br /&gt;    def inspect; "&lt;#{self.class} #{to_s}&gt;"; end&lt;br /&gt;  end&lt;br /&gt;  &lt;br /&gt;  #Bare minimum JSON support to talk to the service&lt;br /&gt;  module MiniJSON&lt;br /&gt;    # Encode an object - we only support strings, numbers, and hashes&lt;br /&gt;    def self.encode(x)&lt;br /&gt;      case x&lt;br /&gt;      when String&lt;br /&gt;        "\"#{x.gsub('"','\\"')}\""&lt;br /&gt;      when Numeric&lt;br /&gt;        x.to_s&lt;br /&gt;      when Hash&lt;br /&gt;        "{" + x.map{|k,v| encode(k)+": "+encode(v)}.join(", ") + "}"&lt;br /&gt;      when NilClass&lt;br /&gt;        "\"\""&lt;br /&gt;      else&lt;br /&gt;        raise "Can't encode #{x.class}"&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;    # Decode an object - only supports a single string literal&lt;br /&gt;    def self.decode(x)&lt;br /&gt;      x.strip!&lt;br /&gt;      if x.length &gt; 1 and x[0]==?" and x[-1]==?" and x[-2]!=?\\&lt;br /&gt;        x[1..-2].gsub('\\"','"').gsub("\\'","'").gsub(/\\u([a-fA-F0-9]{4})/) {[$1.hex].pack("U") }&lt;br /&gt;      else&lt;br /&gt;        raise "Can't decode #{x.inspect}"&lt;br /&gt;      end&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;</description>
      <pubDate>Fri, 13 Apr 2007 00:38:08 GMT</pubDate>
      <guid>http://snippets.dzone.com/posts/show/3811</guid>
      <author>tunah (Sam McCall)</author>
    </item>
  </channel>
</rss>
