code: purgatorio

ref: 87d72e7e8614d96b4f61adae5fb05b0534231c4c
dir: /appl/wm/brutus/excerpt.b/

View raw version
implement Brutusext;

# <Extension excerpt file [start [end]]>

Name:	con "Brutus entry";

include "sys.m";
	sys: Sys;

include "draw.m";
	draw: Draw;
	Context: import draw;

include	"bufio.m";
	bufio: Bufio;
	Iobuf: import bufio;

include	"regex.m";
	regex: Regex;

include "tk.m";
	tk: Tk;

include	"tkclient.m";
	tkclient: Tkclient;

include	"brutus.m";
include	"brutusext.m";

init(s: Sys, d: Draw, b: Bufio, t: Tk, w: Tkclient)
{
	sys = s;
	draw = d;
	bufio = b;
	tk = t;
	tkclient = w;
	regex = load Regex Regex->PATH;
}

create(parent: string, t: ref Tk->Toplevel, name, args: string): string
{
	(text, err) := gather(parent, args);
	if(err != nil)
		return err;
	err = tk->cmd(t, "text "+name+" -tabs {1c} -wrap none -font /fonts/pelm/latin1.9.font");
	if(len err > 0 && err[0] == '!')
		return err;
	(n, maxw) := nlines(text);
	if(maxw < 40)
		maxw = 40;
	if(maxw > 70)
		maxw = 70;
	tk->cmd(t, name+" configure -height "+string n+".01h -width "+string maxw+"w");
	return tk->cmd(t, name+" insert end '"+text);
}

gather(parent, args: string): (string, string)
{
	argl := tokenize(args);
	nargs := len argl;
	if(nargs == 0)
		return (nil, "usage: excerpt [start] [end] file");
	file := hd argl;
	argl = tl argl;
	b := bufio->open(fullname(parent, file), Bufio->OREAD);
	if(b == nil)
		return (nil, sys->sprint("can't open %s: %r", file));
	start := "";
	end := "";
	if(argl != nil){
		start = hd argl;
		if(tl argl != nil)
			end = hd tl argl;
	}
	(text, err) := readall(b, start, end);
	return (text, err);
}

tokenize(s: string): list of string
{
	l: list of string;
	i := 0;
	a := "";
	first := 1;
	while(i < len s){
		(a, i) = arg(first, s, i);
		if(a != "")
			l = a :: l;
		first = 0;
	}
	rl: list of string;
	while(l != nil){
		rl = hd l :: rl;
		l = tl l;
	}
	return rl;
}

arg(first: int, s: string, i: int): (string, int)
{
	while(i<len s && (s[i]==' ' || s[i]=='\t'))
		i++;
	if(i == len s)
		return ("", i);
	j := i+1;
	if(first || s[i] != '/'){
		while(j<len s && (s[j]!=' ' && s[j]!='\t'))
			j++;
		return (s[i:j], j);
	}
	while(j<len s && s[j]!='/')
		if(s[j++] == '\\')
			j++;
	if(j == len s)
		return (s[i:j], j);
	return (s[i:j+1], j+1);
}

readall(b: ref Iobuf, start, end: string): (string, string)
{
	revlines : list of string = nil;
	appending := 0;
	lineno := 0;
	for(;;){
		line := b.gets('\n');
		if(line == nil)
			break;
		lineno++;
		if(!appending){
			m := match(start, line, lineno);
			if(m < 0)
				return (nil, "error in pattern");
			if(m)
				appending = 1;
		}
		if(appending){
			revlines = line :: revlines;
			if(start != ""){
				m := match(end, line, lineno);
				if(m < 0)
					return (nil, "error in pattern");
				if(m)
					break;
			}
		}
	}
	return (prep(revlines), "");
}

prep(revlines: list of string) : string
{
	tabstrip := -1;
	for(l:=revlines; l != nil; l = tl l) {
		s := hd l;
		if(len s > 1) {
			n := nleadtab(hd l);
			if(tabstrip == -1 || n < tabstrip)
				tabstrip = n;
		}
	}
	# remove tabstrip tabs from each line
	# and concatenate in reverse order
	ans := "";
	for(l=revlines; l != nil; l = tl l) {
		s := hd l;
		if(tabstrip > 0 && len s > 1)
			s = s[tabstrip:];
		ans = s + ans;
	}
	return ans;
}

nleadtab(s: string) : int
{
	slen := len s;
	for(i:=0; i<slen; i++)
		if(s[i] != '\t')
			break;
	return i;
}

nlines(s: string): (int, int)
{
	n := 0;
	maxw := 0;
	w := 0;
	for(i:=0; i<len s; i++) {
		if(s[i] == '\n') {
			n++;
			if(w > maxw)
				maxw = w;
			w = 0;
		}
		else if(s[i] == '\t')
			w += 5;
		else
			w++;
	}
	if(len s>0 && s[len s-1]!='\n') {
		n++;
		if(w > maxw)
			maxw = w;
	}
	return (n, maxw);
}

match(pat, line: string, lineno: int): int
{
	if(pat == "")
		return 1;
	case pat[0] {
	'0' to '9' =>
		return int pat <= lineno;
	'/' =>
		if(len pat < 3 || pat[len pat-1]!='/')
			return -1;
		re := compile(pat[1:len pat-1]);
		if(re == nil)
			return -1;
		match := regex->execute(re, line);
		return match != nil;
	}
	return -1;
}

pats: list of (string, Regex->Re);

compile(pat: string): Regex->Re
{
	l := pats;
	while(l != nil){
		(p, r) := hd l;
		if(p == pat)
			return r;
		l = tl l;
	}
	(re, nil) := regex->compile(pat, 0);
	pats = (pat, re) :: pats;
	return re;
}

cook(parent: string, nil: int, args: string): (ref Brutusext->Celem, string)
{
	(text, err) := gather(parent, args);
	if(err != nil)
		return (nil, err);
	el1 := ref Brutusext->Celem(Brutusext->Text, text, nil, nil, nil, nil);
	el2 := ref Brutusext->Celem(Brutus->Type*Brutus->NSIZE+Brutus->Size10, "", el1, nil, nil, nil);
	el1.parent = el2;
	ans := ref Brutusext->Celem(Brutus->Example, "", el2, nil, nil, nil);
	el2.parent = ans;
	return (ans, "");
}

fullname(parent, file: string): string
{
	if(len parent==0 || (len file>0 && (file[0]=='/' || file[0]=='#')))
		return file;

	for(i:=len parent-1; i>=0; i--)
		if(parent[i] == '/')
			return parent[0:i+1] + file;
	return file;
}