From 05e19814d6699177255c9d17a3b5acf21faff7c9 Mon Sep 17 00:00:00 2001 From: Ward Wouts Date: Sat, 27 Apr 2002 20:31:59 +0000 Subject: [PATCH] First add of ripnews to cvs --- trunk/ripnews/news/newsrc.rb | 305 ++++++++++++ trunk/ripnews/ripnews.rb | 519 ++++++++++++++++++++ trunk/ripnews/set/intspan.rb | 926 +++++++++++++++++++++++++++++++++++ 3 files changed, 1750 insertions(+) create mode 100644 trunk/ripnews/news/newsrc.rb create mode 100755 trunk/ripnews/ripnews.rb create mode 100644 trunk/ripnews/set/intspan.rb diff --git a/trunk/ripnews/news/newsrc.rb b/trunk/ripnews/news/newsrc.rb new file mode 100644 index 0000000..f9f61d6 --- /dev/null +++ b/trunk/ripnews/news/newsrc.rb @@ -0,0 +1,305 @@ +################################# +# +# newsrc.rb +# ported from Perl code by Ward Wouts +# this software is released under the terms of +# the GNU Library General Public License +# +# (C) 2001, Ward Wouts +# +################################# + +require "set/intspan" + +module News + +class Newsrc + +def initialize(file=nil) + @newsrc = { "group" => Hash.new, "list" => Array.new } + if file + unless load(file) + print "Can't load #{file}\n" + exit + end + end +end + +def load(file=nil) + file = "#{ENV['HOME']}/.newsrc" unless file + @newsrc["file"] = file + @newsrc["group"] = {} + @newsrc["list"] = [] + + lines = IO.readlines("#{file}") + import_rc(lines) + + return true +end + +def import_rc(lines) + @newsrc["group"] = {} + @newsrc["list"] = [] + linenumber = 1 + for line in lines + parse(line) + end +end + +def parse(line) + unless line =~ /^([^!:]+)([!:])\s(.*)$/x + print "Newsrc.parse: Bad newsrc line: #{line}\n" + exit + end + + name = $1 + mark = $2 + articles = $3 + + unless Set::IntSpan.valid(articles) + print "Newsrc.parse: Bad article list: #{line}\n" + end + + + group = { "name" => name, "subscribed" => (mark == ":"), + "articles" => Set::IntSpan.new(articles)} + + @newsrc["group"][name] = group + @newsrc["list"] += [ group ] +end + +def save + unless @newsrc.has_key?("file") + @newsrc["file"] = "#{$ENV['HOME']}/.newsrc" + end + save_as(@newsrc["file"]) +end + +def save_as(file) + if FileTest.exists?("#{file}") + begin + File.rename(file, "#{file}.bak") + rescue + print "Can't rename #{file}, #{file}.bak: #{$!}\n" + exit + end + end + begin + newsrc = File.new(file, "w") + rescue + print "Can't open #{file}: #{$!}\n" + exit + end + @newsrc["file"] = file + for group in @newsrc["list"] + newsrc.print format(group) + end +end + +def format(group) + name = group["name"] + sub = group["subscribed"] ? ':' : '!' + articles = group["articles"].run_list + space = articles ? ' ' : '' + return "#{name}#{sub}#{space}#{articles}\n" +end + +def export_rc + lines = @newsrc["list"].collect{ |group| + name = group["name"] + sub = group["subscribed"] ? ':' : '!' + articles = group["articles"].run_list + space = articles ? ' ' : '' + "#{name}#{sub}#{space}#{articles}\n" } + return lines +end + +def add_group(name, options) + + if @newsrc["group"].has_key?(name) + options.has_key?("replace") or return false + del_group(name) + end + group = {"name" => name, + "subscribed" => 1, + "articles" => Set::IntSpan.new } + + @newsrc["group"][name] = group + _insert(group, options) + + return true +end + +def move_group(name, options) + if @newsrc["group"].has_key?(name) + group = @newsrc["group"][name] + else + return false + end + + @newsrc["list"] = @newsrc["list"].delete_if{|x| x["name"] == name} + + _insert(group, options) + return true +end + +def _insert(group, options) + list = @newsrc["list"] + + where = "" + arg = "" + if options.has_key?("where") + where = options["where"] + end + arg = where.slice!(1) if where.type.to_s == "Array" + + case where.to_s + when "first" + @newsrc["list"].unshift(group) + when "last" + @newsrc["list"] += [ group ] + when "" + @newsrc["list"] += [ group ] # default + when "alpha" + alpha(group) + when "before" + print "metsj\n" + before(group, arg) + when "after" + after(group, arg) + when "number" + number(group, arg) + end +end + +def alpha (group) + name = group["name"] + for i in (0...@newsrc["list"].length) + print @newsrc["list"][i]["name"],"\n" + if ((name <=> @newsrc["list"][i]["name"]) == -1) +# splice @$list, $i, 0, $group + upper = @newsrc["list"].slice!(i..@newsrc["list"].length) + @newsrc["list"] += [ group ] + @newsrc["list"] += upper + return; + end + end + @newsrc["list"] += [group] +end + +def before(group, before) + print "before\n" + name = group["name"] + + + for i in (0...@newsrc["list"].length) + print @newsrc["list"][i]["name"],"\n" + if (@newsrc["list"][i]["name"] == before.to_s) + print @newsrc["list"][i]["name"],"\n" + upper = @newsrc["list"].slice!(i..@newsrc["list"].length) + @newsrc["list"] += [ group ] + @newsrc["list"] += upper + + return; + end + end + + @newsrc["list"] += [group] +end + +def after(group, after) + name = group["name"] + + for i in (0...@newsrc["list"].length) + if (@newsrc["list"][i]["name"] == after.to_s) + print @newsrc["list"][i+1]["name"],"\n" + upper = @newsrc["list"].slice!((i+1)..@newsrc["list"].length) + @newsrc["list"] += [ group ] + @newsrc["list"] += upper + return; + end + end + + @newsrc["list"] += [group] +end + +def number(group, offset) + offset = @newsrc["list"].length if offset[0] > @newsrc["list"].length + upper = @newsrc["list"].slice!(offset..@newsrc["list"].length) + @newsrc["list"] += [ group ] + @newsrc["list"] += upper +end + + +def del_group(name) + if @newsrc["group"].has_key?(name) + group = @newsrc["group"][name] + else + return false + end + + @newsrc["group"].delete(name) + @newsrc["list"] = @newsrc["list"].delete_if{|x| x["name"] == name} + + return true +end + +def subscribe(name, options = {"where" => ""}) + unless @newsrc["group"].has_key?(name) + add_group(name, options) + end + @newsrc["group"][name]["subscribed"] = true +end + +def unsubscribe(name, options = {"where" => ""}) + unless @newsrc["group"].has_key?(name) + add_group(name, options) + end + @newsrc["group"][name]["subscribed"] = false +end + +end # class + +end # module + + +# TODO +# Do not kill an item until it's tested! + +# [x] new +# [x] load +# [ ] _scan # Initializes a Newsrc object from a string. Used for testing. +# [x] import_rc +# [x] parse # parses a single line from a newsrc file +# [x] save +# [x] save_as +# [x] format +# [x] export_rc +# [ ] _dump # Formats a Newsrc object to a string. Used for testing +# [x] add_group +# [x] move_group +# [x] Splice(\@$$@) # heet nu number en is simpeler +# [x] _insert +# [x] Alpha +# [x] Before +# [x] After +# [x] del_group +# [x] subscribe +# [x] unsubscribe +# [ ] mark +# [ ] mark_list +# [ ] mark_range +# [ ] unmark +# [ ] unmark_list +# [ ] unmark_range +# [ ] exists +# [ ] subscribed +# [ ] marked +# [ ] num_groups +# [ ] groups +# [ ] sub_groups +# [ ] unsub_groups +# [ ] marked_articles +# [ ] unmarked_articles +# [ ] get_articles +# [ ] set_articles diff --git a/trunk/ripnews/ripnews.rb b/trunk/ripnews/ripnews.rb new file mode 100755 index 0000000..007fd4d --- /dev/null +++ b/trunk/ripnews/ripnews.rb @@ -0,0 +1,519 @@ +#!/usr/local/bin/ruby + +require 'net/nntp' +require 'news/newsrc' +require 'date' +require 'getoptlong' + +class Articles + +Debuglevel = 1 + +def initialize(server) + @ids = [] + @subjects = [] + @sorted = false + @grouped = false + @agroups = {} + @nntp = Net::NNTP.new(server) +end + +def add(id, subject) + @ids += [id] + @subjects += [subject] + @sorted = false + @grouped = false +end + +def get_articles(group) + resp, count, first, last,name = @nntp.group(group) + for i in (first.to_i..last.to_i) + begin + @nntp.stat(i) + resp, nr, messid, list = @nntp.head(i) + for j in list + if j =~ /Subject: (.*)/ + subj=$1 + end + end + add(messid, subj) + rescue Net::NNTP::RuntimeError + print "whoopsie couldn't stat #{i}\n" if Debuglevel > 0 + end + end +end + +def get_groups + group_subjects unless @grouped + return @agroups +end + +def get_group_body(subj) + result = [] + for i in @agroups[subj][1..@agroups[subj].length] + resp, nr, id, list = @nntp.body(i) + result = list + end + return result +end + +def get_group_body_first(subj) + resp, nr, id, list = @nntp.body(@agroups[subj][1]) + print "getting article: #{subj}\n" if Debuglevel > 0 + print "article id: #{id}\n" if Debuglevel > 0 + return list +end + +def get_group_body_rest(subj, file=nil) + result = [] + for i in @agroups[subj][2..@agroups[subj].length] + print "getting article: #{i}\n" if Debuglevel > 0 + resp, nr, id, list = @nntp.body(i) + if file + for line in list + file.print "#{line}\n" + end + else + result += list + end + end + return result +end + +def get_group_subjects + group_subjects unless @grouped + return @agroups.keys +end + +def group_complete(subj) + group_subjects unless @grouped + print "length: #{@agroups[subj].length} total: #{@agroups[subj][0].to_i}\n" if Debuglevel > 0 + if (@agroups[subj].length - 1 ) >= @agroups[subj][0].to_i + return true + else + return false + end +end + +def get_ids + return @ids +end + +def get_subjects + return @subjects +end + +def group_subjects + @agroups = {} + subject_sort unless @sorted + prev_subj = "" + for i in (0..@subjects.length) + if @subjects[i] =~ /(.*)\((\d+)\/(\d+)\)(.*)/ || @subjects[i] =~ /(.*)\[(\d+)\/(\d+)\](.*)/ + j = "#{$1}#{$4}" + number = $2 + total = $3 + else + j = @subjects[i] + number = 1 + total = 1 + end + if j == prev_subj + @agroups[j] += [ @ids[i] ] + else + unless number == 0 + prev_subj = j + @agroups[j] = [ total, @ids[i] ] + end + end + end + @grouped = true +end + +def uudecode(data, outfile=nil) + case data.type.to_s + when "Array" + print "Calling _uudecode_array\n" if Debuglevel>0 + mode, file, body = _uudecode_array(data) + when "File" + unless outfile + print "uudecode: need outfile\n" + exit + end + print "Calling _uudecode_file\n" if Debuglevel>0 + mode, file, body = _uudecode_file(data, outfile) + end + return mode, file, body +end + +def _uudecode_file(file, outfile) + mode = 0600 + filename = "unknown" + c = 0 + lines = file.pos + percent = 0 + mark = lines/100 + file.pos=0 + + while (! file.eof) + line = file.gets + print "line: #{line}" if Debuglevel > 0 + if line =~ /^begin(.*)/ + m = $1 + print "beginning matched; rest: #{m}\n" + if m =~ /^(\s+(\d+))?(\s+(.*?\S))?\s*\Z/ + mode = $2 + filename = $4 + print "found beginning\n" if Debuglevel > 0 + else + print "mode, file set to defaults: #{m}\n" + end + break + end + end + + print "not uuencoded!\n" if file.eof + print "c: #{c} mark: #{mark} lines: #{lines}\n" if Debuglevel > 1 + + print "UUdecoding...\n" + + while (! file.eof) + if Debuglevel > 1 + c = file.pos + if c > mark + print "#{percent}%\n" + print "c: #{c} mark: #{mark} lines: #{lines}\n" if Debuglevel > 1 + percent += 1 + mark = (lines/100)*(percent+1) + end + end + line = file.gets + print "line: #{line}" if Debuglevel > 1 + return mode, filename if line =~ /^end/ + next if line =~ /[a-z]/ + next if line == nil + next unless ((((line[0] - 32) & 077) + 2) / 3).to_i == + (line.length/4).to_i + outfile.print line.unpack("u") + end + + print "No \"end\" found!!!\n" + return mode, file, outfile + +end + +# gaat volgens mij niet verder als er meerdere uuencoded blocks zijn... +# zal dan meerdere keren aangeroepen moeten worden, grmbl... +# tis getting a mess as we speak... +# toch maar een keer aparte class van maken... +def _uudecode_array(data) + decode = [] +# begun = false + mode = 0600 + file = "unknown" + c = 0 + lines = data.length + percent = 0 + mark = lines/100 + + i = 0 + print "data.length #{data.length}\n" + while (i < data.length) + print "i #{i}\n" + if data[i] =~ /^begin(.*)/ + m = $1 + print "beginning matched; rest: #{m}\n" + if m =~ /^(\s+(\d+))?(\s+(.*?\S))?\s*\Z/ + mode = $2 + file = $4 + print "found beginning\n" if Debuglevel > 0 + else + print "mode, file set to defaults: #{m}\n" + end + break + end + i += 1 + end + + unless (i < data.length) + print "not uuencoded!\n" + end + + print "UUdecoding...\n" + + while (i < data.length) + if Debuglevel > 1 + if c > mark + print "#{percent}%\n" + print "c: #{c} mark: #{mark} lines: #{lines} i: #{i}\n" if Debuglevel > 1 + percent += 1 + mark = (lines/100)*(percent+1) + end + c += 1 + end + line = data[i] + i += 1 + return mode, file, decode if line =~ /^end/ + next if line =~ /[a-z]/ + next if line == nil + next unless ((((line[0] - 32) & 077) + 2) / 3).to_i == + (line.length/4).to_i + decode += line.unpack("u") + end + + print "No \"end\" found!!!\n" + return mode, file, decode +end + +def uudecode_group(subj, file=nil, outfile=nil) + group_subjects unless @grouped + + body = get_group_body_first(subj) + if body.to_s =~ /begin/ + print "uuencoded!\n" if Debuglevel > 0 + if (file and outfile) + for i in body + file.print "#{i}\n" + end + get_group_body_rest(subj, file) + mode, filename, result = uudecode(file, outfile) + else + body += get_group_body_rest(subj) + mode, filename, result = uudecode(body) + end + return mode, filename, result + else + print "Not uuencoded!\n" if Debuglevel > 0 + return false + end +end + +############################################################### + +def subject_sort + sort_arr = [] + for i in (0..@subjects.length) + sort_arr += ["#{@subjects[i]} #{@ids[i]}"] + end + sort_arr.sort!{|a,b| ward_sort(a, b)} + @ids = [] + @subjects = [] + for i in sort_arr + i =~ /^(.*) (<[^<]*>)$/ || i =~ /^(.*) \[<[^<]*>\]$/ + @ids += [$2] + @subjects += [$1] + end + @sorted = true +end + +def ward_sort(a, b) + a =~ /^(.*) (<[^<]*>)$/ + c = $1.to_s.split(/([0-9]+)/) + b =~ /^(.*) (<[^<]*>)$/ + d = $1.to_s.split(/([0-9]+)/) + + for x in c + y = d.shift + r = ((x.to_s =~ /^[0-9]+$/) && (y.to_s =~ /^[0-9]+$/)) ? + (x.to_i <=> y.to_i) : + (x.to_s <=> y.to_s) + if r != 0 + return r + end + end + return -1 if (d) + return 0 +end + +def quit + @nntp.quit +end + + private :ward_sort + +end + +########################################################################### +########################################################################### + +Debuglevel = 1 + +def save_file(dir, name, data) + print "savename: #{name}\n" if Debuglevel > 1 + nname = name.gsub(/\//, "-") + print "nname: #{nname}\n" if Debuglevel > 1 + newname = nname + count = 1 + d = Date.today + date = "#{d.year}#{d.month}#{d.mday}" + while FileTest.exists?("#{dir}/#{newname}") + newname = "#{nname}-<#{date}.#{count}>" + count += 1 + end + print "name: #{newname}\n" if Debuglevel > 1 + + case data.type.to_s + when "String" + if File.rename(data, "#{dir}/#{newname}") + print "Saving: #{newname}\n" + else + print "couldn't rename tempfile\n" + end + when "Array" + if file = File.new("#{dir}/#{newname}", "w", "0644") + print "Saving: #{newname}\n" + for i in data + file.print "#{i}" + end + else + print "couldn't open file for writeing\n" + end + else + print "EEEEPS\n" + end +end + +def tmp_file(dir) + name = "riptmp" + print "tmpname: #{name}\n" if Debuglevel > 1 + nname = name.gsub(/\//, "-") + print "nname: #{nname}\n" if Debuglevel > 1 + newname = nname + count = 1 + + while FileTest.exists?("#{dir}/#{newname}") + newname = "#{nname}-#{count}" + count += 1 + end + print "name: #{newname}\n" if Debuglevel > 1 + fullname ="#{dir}/#{newname}" + if file = File.new(fullname, "w+", "0644") + return file, fullname + else + print "couldn't open tempfile for writeing\n" + end +end + + +def parse_options + options = {} + + begin + opts = GetoptLong.new( + [ "-I", "--include", GetoptLong::REQUIRED_ARGUMENT ], + [ "-c", "--configfile", GetoptLong::REQUIRED_ARGUMENT ], + [ "-L", "--longname", GetoptLong::NO_ARGUMENT ], + [ "-S", "--singlepart", GetoptLong::NO_ARGUMENT ], + [ "-g", "--greedy", GetoptLong::NO_ARGUMENT ] + ) + opts.quiet=true + + opts.each do |opt, arg| + options[opt] = arg + end + + rescue + print "#{opts.error_message}\n" + print "\nUsage:\n" + exit + end + + return options +end + +def read_config(options) + unless options.has_key?("-c") + options["-c"]=".ripnewsrc" + end + begin + config = IO.readlines("#{options[\"-c\"]}") + for i in config + if i =~ /^OPT_(.)=?(.*)$/ + options["-#{$1}"]=$2 unless + options.has_key?("-#{$1}") + else + i =~ /([^=]*)=(.*)/ + options["#{$1}"]=$2 + end + print "#{$1}=", options["#{$1}"], "\n" if Debuglevel > 1 + end + rescue + print "Coudn't open config file: #{options[\"-c\"]}\n" + exit + end + return options +end + +def check_options(options) + if (Debuglevel > 1) + for i in options.keys + print "Opt: #{i} Value: #{options[i]}\n" + end + end + unless options.has_key?("-I") + print "No inclusions given. Won't match anything.\n" + exit + end + unless options.has_key?("DATADIR") + options["DATADIR"] ="." + end +# exit # zeer tijdelijk voor snel testen +end + +options = {} +options = parse_options +options = read_config(options) +check_options(options) + +if Debuglevel > 1 + for i in options.keys + print "Opt: #{i} val: #{options[i]}\n" + end +end + +articles = Articles.new(options["NNTPSERVER"]) +articles.get_articles("alt.binaries.e-book.flood") + + +for i in articles.get_group_subjects + if i =~ /#{options["-I"]}/i + print "Match: #{i}\n" if Debuglevel > 0 + if articles.group_complete(i) + print "Complete: #{i}\n" if Debuglevel > 0 + if options.has_key?("TMPDIR") + file, fullname = tmp_file(options["TMPDIR"]) + fileout, fullnameout = tmp_file(options["TMPDIR"]) + else + file=nil + fullname = "" + fileout=nil + fullnameout = "" + end + mode, filename, body = articles.uudecode_group(i, file, fileout) + + if file + file.close + fileout.close + body = fullnameout + end + + if mode + print "mode: #{mode}\n" if Debuglevel > 0 + print "filename: #{filename}\n" + if options.has_key?("-L") + print "longname\n" if Debuglevel > 1 + save_file(options["DATADIR"], i, body) + else + print "shortname\n" if Debuglevel > 1 + save_file(options["DATADIR"], filename, body) + end + end + # rm file & fileout + File.delete(fullname) + else + print "Not complete: #{i}\n" + end + print "\n" + end +end + +articles.quit diff --git a/trunk/ripnews/set/intspan.rb b/trunk/ripnews/set/intspan.rb new file mode 100644 index 0000000..06b84e1 --- /dev/null +++ b/trunk/ripnews/set/intspan.rb @@ -0,0 +1,926 @@ +################################# +# +# intspan.rb +# ported from Perl code by Ward Wouts +# this software is released under the terms of +# the GNU Library General Public License +# +# (C) 2001, Ward Wouts +# +################################# + +module Set + +class IntSpan + +Empty_String = '-' +Debuglevel = 0 + +def initialize(setspec=nil) + @set = { "empty_string" => Empty_String } + print "initialize: Calling copy\n" if Debuglevel > 0 + copy(setspec) +end + +def IntSpan.valid(run_list) + testset = new + begin + testset._copy_run_list(run_list) + rescue SystemExit + return false + end + return true +end + +def copy(set_spec) + print "Copy #{set_spec.type.to_s}\n" if Debuglevel > 0 + case set_spec.type.to_s + when "NilClass" + print "copy: Calling _copy_empty\n" if Debuglevel > 0 + _copy_empty + when "String" + print "copy: Calling _copy_run_list\n" if Debuglevel > 0 + _copy_run_list(set_spec) + when "Array" + print "copy: Calling _copy_array\n" if Debuglevel > 0 + _copy_array(set_spec) + when "Set::Intspan" + print "copy: Calling _copy_set\n" + _copy_set(set_spec) + when "Hash" + print "copy: Calling _copy_set\n" + _copy_set(set_spec) + else + print "eeps\n" + end +end + +def _copy_empty # makes @set the empty set + @set = { "negInf" => false } + @set["posInf"] = false + @set["edges"] = [] + @set["run"] = [] +end + +def _copy_array(array) # copies an array into @set + @set["negInf"] = false + @set["posInf"] = false + + print "scary thingy gets called!!!\n" + edges = [] + for element in array.sort + next if edges and edges[-1] == element; # skip duplicates + + if (edges and edges[-1] == $element-1) + edges[-1] = $element; + else + push @edges, $element-1, $element; + end + end + + @set["edges"] = edges + @set["run"] = [] +end + +def _copy_set(src) # copies one set to another + @set["negInf"] = src.neg_inf + @set["posInf"] = src.pos_inf + @set["edges"] = src.edges + @set["run"] = [] +end + +def _copy_run_list(runlist) + + _copy_empty + + runlist.gsub!(/\s|_/, '') + return true if runlist == "" + + + print "copy run list...\n" if Debuglevel > 0 + + first = true + last = false + + edges = [] + + for i in runlist.split(/,/) + print "#{i}\n" if Debuglevel > 0 + begin + if i =~ /^(-?\d+)$/x + edges += [ ($1.to_i-1), $1.to_i ] + next + end + + if i =~ /^ (-?\d+) - (-?\d+) $/x + if $1.to_i > $2.to_i + print "match rule 1 #{$1} > #{$2}\n" + print "Set::IntSpan::_copy_run_list: Bad order: #{runlist}\n" + exit + else + edges += [ ($1.to_i-1), $2.to_i ] + next + end + end + + if i =~ /^\(-(-?\d+)$/x + unless first + print "match rule 2\n" + print "Set::IntSpan::_copy_run_list: Bad order: #{runlist}\n" + exit + end + @set = {"negInf" => true} + edges += [ $1.to_i ] + next + end + + if i =~ /^(-?\d+)-\)$/x + print "match rule 3\n" + edges += [ ($1.to_i-1) ] + @set = {"posInf" => true} + last = true + next + end + + if i =~ /^\(-\)$/x + unless first + print "match rule 4\n" + print "Set::IntSpan::_copy_run_list: Bad order: #{runlist}\n" + exit + end + @set = {"negInf" => true} + @set = {"posInf" => true} + last = true + next + end + + print "no match! \"#{i}\"\n" + print "Set::IntSpan::_copy_run_list: Bad syntax: #{runlist}\n" + end + first = false + end + + @set["edges"] = edges + @set["run"] = [] + + return true +end + +# check for overlapping runs +# delete duplicate edges +def _cleanup + edges = @set["edges"] + + for i in (0..(edges.length-1)) + cmp = edges[i] <=> edges[i+1]; + begin + case cmp + when -1 + i = i + 1 + break + when 0 + edges.slice!(i..(i+1)) + break + when 1 + return 0 + end + end + end + + 1 +end + +#def splice(array, offset, length=nil, list=[]) +# if offset >= 0 +# length = array.length-offset unless length +# leftarray = array.slice(0, offset) +# rightarray = array.slice(offset+length, (array.length - offset)) +# else +# length = array.length+offset unless length +# leftarray = array.slice(0, (array.length+offset)) +# rightarray = array.slice(array.length+length+offset, array.length+offset) +# end +# +# array = leftarray +# array += list +# array += rightarray if rightarray +# +# return array +#end + +def run_list + if empty + return @set["empty_string"] + end + + print "edges leng: ", @set["edges"].length, "\n" if Debuglevel > 0 + edges = [] + edges = @set["edges"] + runs = [] + + if edges.length > 0 + edges = ['(', edges] if @set["negInf"] + edges += [')'] if @set["posInf"] + + print edges.join("/"),"\n" if Debuglevel > 0 + + while(edges.length>0) + print "edges leng: ", @set["edges"].length, "\n" if Debuglevel > 0 + lower = edges[0] + upper = edges[1] + print "Lower: \"#{lower}\" Upper: \"#{upper}\"\n" if Debuglevel > 0 + edges = edges.slice(2..edges.length) + + if ((lower.to_s <=> '(')!=0 and + (upper.to_s <=> ')')!=0 and + ((lower+1) == upper)) + print "#{upper}\n" if Debuglevel > 0 + runs += [ "#{upper}" ] + else + lower += 1 if (lower.to_s <=> "(")!=0 + print "#{lower}-#{upper}\n" if Debuglevel > 0 + runs += [ "#{lower}-#{upper}" ] + end + end + end + + print "edges leng: ", @set["edges"].length, "\n" if Debuglevel > 0 + + return runs.join(',') +end + +def elements + if (@set["negInf"] == true or @set["posInf"] == true) + print "Set::IntSpan::elements: infinite set\n" + exit + end + + elements = [] + edges = @set["edges"] + while (edges.length>0) + lower, upper = edges.slice!(0..1) + elements += (lower+1 .. upper).to_a + end + + return elements +end + +def _real_set(set_spec=nil) # converts a set specification into a set + (set_spec != nil and set_spec.type.to_s == "Set::IntSpan") ? + set_spec : + IntSpan.new(set_spec) +end + +def union(set_spec) + b = _real_set(set_spec) + s = IntSpan.new + + s.set_neg_inf(@set["negInf"] || b.neg_inf) + + eA = @set["edges"] + eB = b.edges + eS = s.edges + + inA = @set["negInf"] + inB = b.neg_inf + + iA = 0 + iB = 0 + + while (iA < eA.length and iB < eB.length) + xA = eA[iA] + xB = eB[iB] + + if (xA < xB) + iA += 1 + inA = ! inA + not inB and eS += [ xA ] + elsif (xB < xA) + iB += 1 + inB = ! inB + not inA and eS += [ xB ] + else + iA += 1 + iB += 1 + inA = ! inA + inB = ! inB + inA == inB and eS += [ xA ] + end + end + + iA < eA.length and (! inB) and eS += eA[iA..eA.length] + iB < eB.length and (! inA) and eS += eB[iB..eB.length] + + s.set_pos_inf(@set["posInf"] || b.pos_inf) + s.set_edges(eS) + + return s +end + +def intersect(set_spec) + b = _real_set(set_spec) + s = IntSpan.new + + s.set_neg_inf(@set["negInf"] && b.neg_inf) + + eA = @set["edges"] + eB = b.edges + eS = s.edges + + inA = @set["negInf"] + inB = b.neg_inf + + iA = 0 + iB = 0 + + while (iA < eA.length and iB < eB.length) + xA = eA[iA] + xB = eB[iB] + + if (xA < xB) + iA += 1 + inA = ! inA + inB and eS += [ xA ] + elsif (xB < xA) + iB += 1 + inB = ! inB + inA and eS += [ xB ] + else + iA += 1 + iB += 1 + inA = ! inA + inB = ! inB + inA == inB and eS += [ xA ] + end + end + + iA < eA.length and inB and eS += eA[iA..eA.length] + iB < eB.length and inA and eS += eB[iB..eB.length] + + s.set_neg_inf(@set["posInf"] && b.pos_inf) + s.set_edges(eS) + return s +end + +def diff (set_spec) + b = _real_set(set_spec) + s = IntSpan.new + + s.set_neg_inf(@set["negInf"] && ! b.neg_inf) + + eA = @set["edges"] + eB = b.edges + eS = s.edges + + inA = @set["negInf"] + inB = b.neg_inf + + iA = 0 + iB = 0 + + while (iA < eA.length and iB < eB.length) + xA = eA[iA] + xB = eB[iB] + + if (xA < xB) + iA += 1 + inA = ! inA + not inB and eS += [ xA ] + elsif (xB < xA) + iB += 1 + inB = ! inB + inA and eS += [ xB ] + else + iA += 1 + iB += 1 + inA = ! inA + inB = ! inB + inA != inB and eS += xA + end + end + + iA < eA.length and not inB and eS += eA[iA..eA.length] + iB < eB.length and inA and eS += eB[iB..eB.length] + + s.set_edges(eS) + + s.set_pos_inf(@set["posInf"] && ! b.pos_inf) + return s +end + +def xor(set_spec) + b = _real_set(set_spec) + s = IntSpan.new + + s.set_neg_inf(@set["negInf"] ^ b.neg_inf) + + eA = @set["edges"] + eB = b.edges + eS = s.edges + + iA = 0 + iB = 0 + + while (iA < eA.length and iB < eB.length) + xA = eA[iA] + xB = eB[iB] + + if (xA < xB) + iA += 1 + eS += [ xA ] + elsif (xB < xA) + iB += 1 + eS += [ xB ] + else + iA += 1 + iB += 1 + end + end + + iA < eA.length and eS += eA[iA..eA.length] + iB < eB.length and eS += eB[iB..eB.length] + + s.set_pos_inf(@set["posInf"] ^ b.pos_inf) + s.set_edges(eS) + return s +end + +def complement +# complement is inverse set; dit klopt hier dus niet + a = first + b = last + + print "first #{a} last #{b}\n" if Debuglevel > 0 + if a!=b + s = IntSpan.new("#{a}-#{b}") + comp = xor(s) + else + comp = IntSpan.new("#{a}") + end + + if Debuglevel > 0 + while i = comp.next + print "#{i}\n" + end + end + + comp.set_neg_inf(! comp.neg_inf) + comp.set_pos_inf(! comp.pos_inf) + return comp +end + + +def superset(set_spec) + b = _real_set(set_spec) + +# $b->diff($a)->empty + s = b.diff(self) + return s.empty +end + + +def subset(set_spec) + b = _real_set(set_spec) + +# $a->diff($b)->empty + s = diff(b) + return s.empty +end + + +def equal(set_spec) + b = _real_set(set_spec) + +print "a\n" + @set["negInf"] == b.neg_inf or return false +print "b\n" + @set["posInf"] == b.pos_inf or return false + + aEdge = @set["edges"] + bEdge = b.edges + print "aEdge #{aEdge.length} bEdge #{bEdge.length}\n" + aEdge.length == bEdge.length or return false +print "c\n" + + for i in (0...aEdge.length) + aEdge[i] == bEdge[i] or return false + end + + return true +end + +def equivalent(set_spec) + b = _real_set(set_spec) + + cardinality == b.cardinality +end + + +def cardinality + (@set["negInf"] or @set["posInf"]) and return -1 + + car = 0 + edges = @set["edges"] + i=0 + while (i < edges.length) + lower = edges[i] + upper = edges[i+1] + car += upper - lower + i += 2 + end + + return car +end + +def empty + if @set["negInf"] == false and @set["edges"].length > 0 and + @set["posInf"] == false + return false + end + return true +end + +def finite + if @set["negInf"] == false and @set["posInf"] == false + return true + end + return false +end + +def edges + return @set["edges"] +end + +def set_edges(edges) + @set["edges"] = edges +end + +def neg_inf + return @set["negInf"] +end + +def set_neg_inf(negInf) + @set["negInf"] = negInf +end + +def pos_inf + return @set["posInf"] +end + +def set_pos_inf(posInf) + @set["posInf"] = posInf +end + +def infinite + @set["negInf"] or @set["posInf"] +end + +def universal + @set["negInf"] and not @set["edges"].length > 0 and @set["posInf"] +end + +def member(n) + inSet = @set["negInf"] + edge = @set["edges"] + + for i in (0...edge.length) + if inSet + return true if n <= edge[i] + inSet = false + else + return false if n <= edge[i] + inSet = true + end + end + + inSet +end + +def insert(n) + n or return + + inSet = @set["negInf"] + edge = @set["edges"] + + if n > edge[-1]+1 + @set["edges"] += [n-1, n] + return + elsif n > edge[-1] + @set["edges"][-1] += 1 + return + end + + for i in (0...edge.length) + if (inSet) + n <= edge[i] and return + inSet = false + else + n <=edge[i] and break + inSet = true + end + end + + inSet and return + + lGap = i == 0 || n-1 - edge[i-1] + lGap = false if lGap == 0 + + rGap = i == edge.length-1 ? i : edge[i] - n + rGap = false if rGap == 0 + + if ( lGap and rGap) + lower = edge[0...i] + upper = edge[i...edge.length] + edge = lower + edge += [n-1, n] + edge += upper + elsif (not lGap and rGap) + edge[i-1] += 1 + elsif ( lGap and not rGap) + edge[i] -= 1 + else + lower = edge[0...i-1] + upper = edge[i+1..edge.length] + edge = lower + edge += upper + end + + @set["edges"] = edge +end + +def remove(n) + n or return + + inSet = @set["negInf"] + edge = @set["edges"] + + for i in (0...edge.length) + if (inSet) + break if n <= edge[i] + inSet = false + else + return if n <= edge[i] + inSet = true + end + end + + return unless inSet + + for i in (0...edge.length) + if edge[i] == n-1 and edge[i+1] == n + lower = edge[0...i] + upper = edge[i+2..edge.length] + edge = lower + upper + break + elsif edge[i] == n-1 + edge[i] += 1 + break + elsif edge[i] == n + edge[i] += 1 + break + elsif edge[i+1] == n + edge[i+1] -= 1 + break + elsif edge[i]n + lower = edge[0..i] + upper = edge[i+1..edge.length] + edge = lower + [n-1, n] +upper + break + end + i += 1 + end + + @set["edges"] = edge +end + +def min + empty and return nil + neg_inf and return nil + @set["edges"][0]+1 +end + + +def max + empty and return nil + pos_inf and return nil + @set["edges"][-1] +end + +def grep_set(block) + return nil if @set["negInf"] or @set["posInf"] + + edges = @set["edges"] + sub_edges = [] + + while (edges.length > 0) + lower = edges[0] + upper = edges[1] + edges = edges.slice(2..edges.length) + + for i in (lower+1..upper) +# local $_ = i +# &$block() or next # definately wrong, must eval block + + if (sub_edges.length > 0 and sub_edges[-1] == i-1) + sub_edges[-1] = i + else + sub_edges += [ i-1, i ] + end + end + end + + sub_set = new + sub_set["edges"] = sub_edges + sub_set +end + +def map_set(block) + return nil if @set["negInf"] or @set["posInf"] + + map_set = new + + edges = @set["edges"] + while (edges.length > 0) + lower = edges[0] + upper = edges[1] + edges = edges.slice(2..edges.length) + + for domain in (lower+1..upper) + local $_ = domain; + +# for range (&$block()) # definately wrong, must eval block +# map_set.insert(range) +# end + end + end + + map_set +end + +def first + @set["iterator"] = min + @set["run"] = [] + @set["run"][0] = 0 + @set["run"][1] = @set["edges"].length > 0 ? 1 : nil + + @set["iterator"] +end + + +def last + lastEdge = @set["edges"].length - 1 + @set["iterator"] = max + @set["run"][0] = lastEdge > 0 ? lastEdge-1 : nil + @set["run"][1] = lastEdge + + @set["iterator"] +end + +def start(startval) + set["iterator"] = nil + startval or return nil + + inSet = @set["negInf"] + edges = @set["edges"] + + for i in (0...edges.length) + if (inSet) + if (startval <= edges[i]) + @set["iterator"] = startval + @set["run"][0] = i ? i-1 : nil + @set["run"][1] = i + return $startval + end + inSet = false + else + if (startval <= edges[i]) + return nil + end + inSet = true + end + end + + if (inSet) + @set["iterator"] = startval + @set["run"][0] = edges.length > 0 ? edges.length: nil + @set["run"][1] = nil + end + + @set["iterator"] +end + +def current + @set["iterator"] +end + + +def next + @set["iterator"] or return first + + run1 = @set["run"][1] + run1 or return ++@set["iterator"] + + edges = @set["edges"] + if (@set["iterator"] < edges[run1]) + @set["iterator"] += 1 + return @set["iterator"] + end + + if (run1 < edges.length-2) + run0 = run1 + 1 + @set["run"] = [run0, run0+1] + @set["iterator"] = edges[run0]+1 + elsif (run1 < edges.length-1) + run0 = run1 + 1 + @set["run"] = [run0, nil] + @set["iterator"] = edges[run0]+1 + else + @set["iterator"] = nil + end + + @set["iterator"] +end + +def prev + @set["iterator"] or return last + + run0 = @set["run"][0] + run0 or return --@set["iterator"] + + edges = @set["edges"] + + if (@set["iterator"] > edges[run0]+1) + @set["iterator"] -= 1 + return @set["iterator"] + end + + if (run0 > 1) + run1 = run0 - 1 + @set["run"] = [run1-1, run1] + @set["iterator"] = edges[run1] + elsif (run0 > 0) + run1 = run0 - 1 + @set["run"] = [nil, run1] + @set["iterator"] = edges[run1] + else + @set["iterator"] = nil + end + + @set["iterator"] +end + + +end # class + +end # module + + +# TODO +# Do not kill an item until it's tested! + +# [x] new +# [x] valid +# [ ] copy +# [ ] _copy_empty # makes $set the empty set +# [ ] _copy_array # copies an array into a set +# [ ] _copy_set # copies one set to another +# [ ] _copy_run_list # parses a run list +# [ ] _cleanup +# [x] run_list +# [x] elements +# [x] _real_set # converts a set specification into a set +# [x] union +# [x] intersect +# [x] diff +# [x] xor +# [ ] complement +# [x] superset +# [x] subset +# [x] equal +# [x] equivalent +# [x] cardinality +# [x] empty +# [x] finite +# [x] neg_inf { shift->{negInf} } +# [x] pos_inf { shift->{posInf} } +# [x] infinite +# [ ] universal +# [x] member +# [x] insert +# [x] remove +# [x] min +# [x] max +# [ ] grep_set(&$) +# [ ] map_set(&$) +# [x] first($) +# [x] last($) +# [ ] start($$) +# [x] current($) { shift->{iterator} } +# [x] next($) +# [x] prev($) + +# New methods +# [x] set_neg_inf +# [x] set_pos_inf +# [x] set_edges +# [x] edges