#!/usr/local/bin/ruby # (c) COUDERC Damien # 1 December 2002 # under BSD License require 'gtk' VER = "0.1.6" PORTSPATH = '/usr/ports' CDPORTS = "cd #{PORTSPATH}" ROOTMAKE = "#{CDPORTS}; make" PKGPATH = '/var/db/pkg' PKGINFO = '/usr/sbin/pkg_info' PKGDELETE = '/usr/sbin/pkg_delete' SUDO = 'sudo ' #### classes class OBSDPorts def initialize(path) @pathlist = Array.new @portlist = Array.new @porttree = Array.new @nindex = 0 init_tree(path) end def init_tree(path) @pl = parse_tree(path) end def parse_tree(path) if FileTest.exist?(File.join(path, 'distinfo')) # found a port return nil else fn = File.join(path, 'Makefile') if FileTest.exist?(fn) plst = Array.new fid = File.open(fn) isport = false while not fid.eof l = fid.gets.strip if l =~ /^SUBDIR *\+= *(\S+).*/ t, waste = $1.split(',', 2) port, waste = t.split(':', 2) p = File.join(path, port) r = parse_tree(p) case r when nil plst.push([port, nil]) when false # do nothing printf("Found unusable %s\n", p) else plst.push([port, r]) end else if l =~ // isport = true end end end fid.close if isport == true return nil else return plst.uniq end else # empty branch => not usable return false end end end def dir2array(path) a = Dir.entries(path) a.delete('.') a.delete('..') return a end def get_list return @pl end end class OBSDPkg def initialize @pkglist = dir2array(PKGPATH).sort end def get_list return @pkglist end def get_pkg_info(pkgname) plst = dir2array(File.join(PKGPATH, pkgname)) a = Array.new a.push(sprintf("Information for package : %s", pkgname)) a.push('') a.push('Comment :') a.concat(file2array(File.join(PKGPATH, pkgname, '+COMMENT'))) if plst.include?('+REQUIRED_BY') a.push('') a.push('Required by :') a.concat(file2array(File.join(PKGPATH, pkgname, '+REQUIRED_BY'))) end a.push('') a.push('Description :') a.concat(file2array(File.join(PKGPATH, pkgname, '+DESC'))) return a end def pkg_required(pkgname) path = File.join(PKGPATH, pkgname) plst = dir2array(path) if plst.include?('+REQUIRED_BY') file = File.join(path, '+REQUIRED_BY') if File.stat(file).size? == nil return false else return true end else return false end end def dir2array(path) a = Dir.entries(path) a.delete('.') a.delete('..') return a end def file2array(file) a = Array.new fid = File.open(file, 'r') while not fid.eof a.push(fid.gets.chop) end fid.close return a end end class WinTree def initialize @selected = nil @flavors = Array.new @ptbox = Gtk::VBox.new @btnbox = Gtk::VBox.new @ptbox.pack_start(@btnbox, false, true, 4) potb = Gtk::HBox.new @btnbox.pack_start(potb, false, true, 0) # rbtn = Gtk::Button.new('Refresh') # rbtn.signal_connect('clicked') do # update_tree($ports.get_list) # end # potb.pack_start(rbtn, true, false, 0) mcbtn = Gtk::Button.new("Clean") mcbtn.signal_connect('clicked') do # make build port_make(@selected, get_flavors, 'clean') end potb.pack_start(mcbtn, true, false, 0) mbbtn = Gtk::Button.new("Build") mbbtn.signal_connect('clicked') do # make build port_make(@selected, get_flavors, 'build') end potb.pack_start(mbbtn, true, false, 0) mpbtn = Gtk::Button.new("Package") mpbtn.signal_connect('clicked') do # make package port_make(@selected, get_flavors, 'package') end potb.pack_start(mpbtn, true, false, 0) mibtn = Gtk::Button.new("Install") mibtn.signal_connect('clicked') do # make install port_make(@selected, get_flavors, 'install') end potb.pack_start(mibtn, true, false, 0) mdbtn = Gtk::Button.new('Deinstall') mdbtn.signal_connect('clicked') do # make deinstall port_make(@selected, get_flavors, 'deinstall') end potb.pack_start(mdbtn, true, false, 0) btnsep = Gtk::HSeparator.new @btnbox.pack_start(btnsep, false, true, 4) @fltb = Gtk::HBox.new @btnbox.pack_start(@fltb, false, true, 4) @fltb.pack_start(Gtk::Label.new('flavors'), false, true, 4) @flbx = Gtk::HBox.new @fltb.pack_start(@flbx, false, true, 4) pohb = Gtk::HBox.new @ptbox.pack_start(pohb, true, true, 0) poswtr = Gtk::ScrolledWindow.new(nil, nil) poswtr.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) pohb.pack_start(poswtr, true, true, 0) @rt = Gtk::CTree.new(['ports tree', 'Path'], 0) @rt.set_selection_mode(Gtk::SELECTION_BROWSE) @rt.set_expander_style(Gtk::CTree::EXPANDER_CIRCULAR) @rt.line_style = Gtk::CTree::LINES_SOLID @rt.signal_connect('tree_select_row') do |ctree, node, col| @rt.get_node_info(node) path = @rt.node_get_text(node, col) if path != nil update_info([path]) @selected = path f = port_flavors(path) if f.length != 0 update_flavors(f) end end end poswtr.add_with_viewport(@rt) poswit = Gtk::ScrolledWindow.new(nil, nil) poswit.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) pohb.pack_start(poswit, true, true, 0) @it = Gtk::Text.new poswit.add(@it) end def init_tree(list) list.each do |branch| parse_branch(nil, branch, '') end end def parse_branch(parent, branch, fpath) node, list = branch fp = File.join(fpath, node) cnode = @rt.insert_node(parent, nil, [node, fp], 0, nil, nil, nil, nil, false, false) if list != nil list.each do |nbranch| parse_branch(cnode, nbranch, fp) end end end def getw return @ptbox end def update_info(array) @it.set_point(0) @it.forward_delete(@it.get_length) array.each do |l| @it.insert(nil, nil, nil, l + "\n") end end def update_flavors(flavors) @flavors.clear @flbx.destroy @flbx = Gtk::HBox.new @fltb.pack_start(@flbx, false, true, 4) flavors.each do |f| cb = Gtk::CheckButton.new(f) cb.signal_connect('clicked') do |w| if w.active? == true @flavors.push(f) else @flavors.delete(f) end end @flbx.pack_start(cb, true, false, 4) end @flbx.show_all end def get_flavors return @flavors.join(' ') end end class WinPkg def initialize @pkbox = Gtk::VBox.new pktb = Gtk::HBox.new @pkbox.pack_start(pktb, false, true, 0) # rbtn = Gtk::Button.new('Refresh') # rbtn.signal_connect('clicked') do # update_list($pkglist.get_list) # end # pktb.pack_start(rbtn, true, false, 0) dbtn = Gtk::Button.new('Delete') dbtn.signal_connect('clicked') do @pl.each_selection do |r| pkgname = @pl.get_text(r, 0) if pkg_delete(pkgname) @pl.remove_row(r) end end end pktb.pack_start(dbtn, true, false, 0) pkhb = Gtk::HBox.new @pkbox.pack_start(pkhb, true, true, 0) pkswcl = Gtk::ScrolledWindow.new(nil, nil) pkswcl.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) pkhb.pack_start(pkswcl, true, true, 0) @pl = Gtk::CList.new(['Name']) @pl.set_selection_mode(Gtk::SELECTION_BROWSE) pkswcl.add_with_viewport(@pl) @pl.signal_connect('select_row') do |w, r, c, e| info = $pkglist.get_pkg_info(w.get_text(r, 0)) info = pkg_get_info(w.get_text(r, 0)) update_info(info) end pkswit = Gtk::ScrolledWindow.new(nil, nil) pkswit.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) pkhb.pack_start(pkswit, true, true, 0) @it = Gtk::Text.new pkswit.add(@it) end def init_list(list) list.each do |n| @pl.append([n]) end end def clear_list @pl.clear end def update_list(list) clear_list init_list(list) end def getw return @pkbox end def update_info(info) @it.freeze @it.set_point(0) @it.forward_delete(@it.get_length) info.each do |l| @it.insert(nil, nil, nil, l + "\n") end @it.thaw end end class WinLog def initialize @sw = Gtk::ScrolledWindow.new(nil, nil) @sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) @text = Gtk::Text.new @sw.add(@text) end def getw return @sw end def write(str) @text.insert(nil, nil, nil, str + "\n") end end #### tools def index_parser pipe = IO.popen("#{ROOTMAKE} print-index") while not pipe.eof line = pipe.gets.chop! if line != '' and line != nil key, content = line.split(':', 2) content.strip! case key when 'Port' name = content when 'Path' path, blah = content.split(',') when 'Info' info = content when 'Maint' maint = content when 'Index' index = content.split(' ') when 'L-deps' ldeps = content.split(' ') when 'B-deps' bdeps = content.split(' ') when 'R-deps' rdeps = content.split(' ') end else p = PortObj.new(name, path, info, maint, index, ldeps, bdeps, rdeps) $ports.add_by_path(p) end end pipe.close end def port_make(path, flavor, target) if flavor != '' fstr = sprintf("FLAVOR=\"%s\" ", flavor) else fstr = '' end if path != nil Thread.start do fpath = File.join(CDPORTS, path) cmd = sprintf("/bin/sh -c '%s && %s%smake %s 2>&1'", fpath, fstr, SUDO, target) $winlog.write('##' + cmd) pipe = IO.popen(cmd) while not pipe.eof $winlog.write(pipe.gets.chop) end pipe.close $winlog.write("## End") end else $winlog.write("Nothing selected !") end end def port_flavors(path) # Thread.start do fpath = File.join(CDPORTS, path) cmd = sprintf("%s && %smake show=FLAVORS", fpath, SUDO) pipe = IO.popen(cmd) flavors = pipe.gets.chop.strip pipe.close return flavors.split(' ') # end end def pkg_info_parser end def pkg_get_info(pkgname) return $pkglist.get_pkg_info(pkgname) end def pkg_delete(pkgname) if not $pkglist.pkg_required(pkgname) return system("#{SUDO}#{PKGDELETE} #{pkgname}") else print "Package required !\n" return false end end # Main $ports = OBSDPorts.new(PORTSPATH) $pkglist = OBSDPkg.new $w = Gtk::Window.new(Gtk::WINDOW_TOPLEVEL) $w.set_usize(640, 400) $w.set_title('Ports and packages manager ' + VER) $w.signal_connect("delete_event") do exit end $w.signal_connect("destroy_event") do exit end #$w.realize box = Gtk::VBox.new(false, 0) $w.add(box) $nb = Gtk::Notebook.new box.pack_start($nb, true, true, 0) $wintree = WinTree.new $nb.append_page($wintree.getw, Gtk::Label.new('portstree')) $wintree.init_tree($ports.get_list) $winpkg = WinPkg.new $nb.append_page($winpkg.getw, Gtk::Label.new('packages')) $winpkg.init_list($pkglist.get_list) $winlog = WinLog.new $nb.append_page($winlog.getw, Gtk::Label.new('log')) $winlog.write("Welcome") $w.show_all Gtk.main