#!/usr/bin/env splrun_norl // // svnbrowse.spl - simple svn repository browser and URL picker // Copyright (C) 2008 Claire Xenia Wolf // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. load "stfl"; load "system"; load "array"; load "xml"; var list = [ ]; var list_maxid = -1; function call_system(cmd, status) { stfl_set(f, "status", status); stfl_run(f, -1); // stfl_reset(); return system("LC_ALL=C " ~ cmd); } function add_root(url) { list_maxid++; list[list_maxid] = [ url: url, level: 0, isdir: 1, open: 0, popen: 0, mark: 0 ]; } if (not declared argv or not declared argv[0]) { var xmldoc = xml_parse(system("svn info --xml .")); var baseurl = xmldoc["//root"]; baseurl =~ s/\/*$//; add_root(baseurl); baseurl = xmldoc["//url"]; baseurl =~ s/\/*$//; add_root(baseurl); } else { if (argv[0] !~ /:\/\//) { write("Usage: svnbrowse [URL]\n"); exit; } var baseurl = argv[0]; baseurl =~ s/\/*$//; add_root(baseurl); } var f = stfl_create(<:> : table : vbox : .border:lrtb : label : .expand:0 : text:"Subversion Filesystem Tree:" : list[treeview] : style_focus:bg=blue,fg=white,attr=bold : pos_name[current_item]: : listitem[item_${i}] : text[item_${i}_label]: : tablebr : textview[info] : .expand:0 : .height:10 : .border:lrtb : richtext:1 : style_key_normal:attr=bold,underline : tablebr : label : .expand:0 : text[status]: ); function info(text) { stfl_modify(f, "info", "replace_inner", <:> : textview : listitem : text:${stfl::line} ); } function info_help() { info(<:info> : ENTER .. fold/unfold this part of the tree : SPACE .. mark/unmark entry (merked entries are printed on exit) : : p .. show/hide this nodes properties as additional entries in the tree : p .. show property value in this info window : q .. quit programm and print all marked URLs to standard output ); } function status_seturl() { var status = ""; switch { case stfl_get(f, "current_item") =~ /^item_(\d+)/: status = list[$1].url; case stfl_get(f, "current_item") =~ /^pitem_(\d+)_(\d+)/: status = list[$1].url; } stfl_set(f, "status", status); } function listitem_update(id) { var text = <> ${ list[id].mark ? "!" : " " } ${ not list[id].isdir ? "." : (declared list[id].children and elementsof list[id].children == 0) ? "*" : list[id].open ? "-" : "+" } ${ list[id].level == 0 ? list[id].url : list[id].url =~ s/.*\///R }${ list[id].popen ? " (P)" : ""}; stfl_set(f, "item_${id}_label", text); } function listitem_add(id) { stfl_modify(f, "item_${list[id].parent}", "after", <:> : listitem[item_${id}] : text[item_${id}_label]: ); listitem_update(id); } function listitem_deltree(id) { stfl_modify(f, "item_${id}", "delete"); if (list[id].open) { list[id].open = 0; foreach[] c (list[id].children) { listitem_deltree(c); } } } function listitem_addprop(id, p) { stfl_modify(f, "item_${id}", "after", <:> : listitem[pitem_${id}_${p}] : text:${stfl::<> p ${list[id].props[p]}} ); } function listitem_delprop(id, p) { stfl_modify(f, "pitem_${id}_${p}", "delete"); } function listitem_load(id) { if (declared list[id].children) return; var list[id].children = [ ]; if (not list[id].isdir) return; var xmldoc = xml_parse(call_system("svn ls --xml ${list[id].url}", "Loading file-list from ${list[id].url}..")); foreach[] e (xmldoc["//entry"].nodes) { list_maxid++; var isdir = e.["@kind"] ~== "dir"; list[list_maxid] = [ url: "${list[id].url}/${e.["name"]}", level: list[id].level + 1, isdir: isdir, open: 0, popen: 0, mark: 0, parent: id ]; push list[id].children, list_maxid; } array_sort_by_values(list[id].children, function(a,b) { var a_txt = list[a].url =~ e/([0-9]+)/Rg fmt("%010d", $1); var b_txt = list[b].url =~ e/([0-9]+)/Rg fmt("%010d", $1); return a_txt ~< b_txt; }); array_reindex(list[id].children); } function listitem_loadprops(id) { if (declared list[id].props) return; var list[id].props = [ ]; var list[id].propvals = [ ]; foreach[] line (call_system("svn pl ${list[id].url}", "Loading property-list from ${list[id].url}..") =~ /\n/Sg) { if (line =~ /^\s+(.*)/) push list[id].props, $1; } } for (var i=0; i<=list_maxid; i++) listitem_update(i); stfl_run(f, -3); info_help(); while (1) { switch { status_seturl(); var event = stfl_run(f, 0); stfl_run(f, -3); info_help(); case event ~== "SPACE" and stfl_get_focus(f) ~== "treeview" and stfl_get(f, "current_item") =~ /^item_(\d+)/: var id = $1; list[id].mark = not list[id].mark; listitem_update(id); case event ~== "ENTER" and stfl_get_focus(f) ~== "treeview" and stfl_get(f, "current_item") =~ /^item_(\d+)/: var id = $1; switch { listitem_load(id); case elementsof list[id].children != 0 and not list[id].open: foreach[] c (list[id].children) { listitem_add(c); } list[id].open = 1; case elementsof list[id].children != 0 and list[id].open: foreach[] c (list[id].children) { listitem_deltree(c); } list[id].open = 0; } listitem_update(id); case event ~== "p" and stfl_get_focus(f) ~== "treeview" and stfl_get(f, "current_item") =~ /^item_(\d+)/: var id = $1; switch { listitem_loadprops(id); case not list[id].popen: foreach p (list[id].props) { listitem_addprop(id, p); } list[id].popen = 1; listitem_update(id); case list[id].popen: foreach p (list[id].props) { listitem_delprop(id, p); } list[id].popen = 0; listitem_update(id); } case event ~== "p" and stfl_get_focus(f) ~== "treeview" and stfl_get(f, "current_item") =~ /^pitem_(\d+)_(\d+)/: var id = $1, p = $2; if (not declared list[id].propvals[p]) list[id].propvals[p] = call_system("svn pg ${list[id].props[p]} ${list[id].url}", "Loading property ${list[id].props[p]} from ${list[id].url}.."); info("Property Value:\n${list[id].propvals[p]}"); case event ~== "ESC" or event ~== "q": break; } } stfl_reset(); foreach[] e (list) if (e.mark) write("${e.url}\n"); exit;