diff --git a/src/editor/simple/P/w1s.jpg b/src/editor/simple/P/w1s.jpg new file mode 100644 index 000000000..f91359584 Binary files /dev/null and b/src/editor/simple/P/w1s.jpg differ diff --git a/src/editor/simple/P/w2s.jpg b/src/editor/simple/P/w2s.jpg new file mode 100644 index 000000000..23683a2bb Binary files /dev/null and b/src/editor/simple/P/w2s.jpg differ diff --git a/src/editor/simple/P/w3s.jpg b/src/editor/simple/P/w3s.jpg new file mode 100644 index 000000000..88e2f3451 Binary files /dev/null and b/src/editor/simple/P/w3s.jpg differ diff --git a/src/editor/simple/P/w4s.jpg b/src/editor/simple/P/w4s.jpg new file mode 100644 index 000000000..8c904cdd9 Binary files /dev/null and b/src/editor/simple/P/w4s.jpg differ diff --git a/src/editor/simple/TODO b/src/editor/simple/TODO new file mode 100644 index 000000000..b9a684518 --- /dev/null +++ b/src/editor/simple/TODO @@ -0,0 +1,28 @@ ++ Safety question before deleting a grammar ++ Check identifier syntax ++ Allow lincat for deleted cat to be deleted ++ Allow lin for deleted fun to be deleted ++ Apply category alpha conversion in concrete syntax ++ Remove a concrete syntax ++ Apply function alpha conversion in concrete syntax ++ Change lhs of lins when function type is changed + +- Allow languages other than the ones in the given list to be added ++ Export as plain text +- Allow definitions to be reordered + ++ 1. possibility to compile the grammar set, returning a URL to a translator app +- 2. possibility to import modules - both resource libraries and user's own + auxiliaries +- 3. possibility to upload own modules ++ 4. access to the created files in an on-line shell (making testing possible) +- 5. rule-to-rule type checking and guidance (e.g. with library oper + suggestions) +- Try grammars in the Translation Quiz + ++ compile only the uploaded grammar even if other grammars are present ++ 'flags startcat' is needed for grammars with only one category (since the + default startcat is S, even if it doesn't exist) + +- Bug! After adding a 2nd def of a fun with a different type and then deleting + the old fun, the corresponding lin will have the wrong lhs. diff --git a/src/editor/simple/about.html b/src/editor/simple/about.html new file mode 100644 index 000000000..337c453b0 --- /dev/null +++ b/src/editor/simple/about.html @@ -0,0 +1,157 @@ + + + +About: GF online editor for simple multilingual grammars + + + + + + + + + + + + + +

GF online editor for simple multilingual grammars

+ +
+
+ [GF online editor screen shoot] + + + +
+
+

About

+ +Traditionally, GF +grammars are created in a text editor and tested in the +GF shell. Text editors know very little (if anything) about the syntax of +GF grammars, and thus provide little guidance for novice GF users. Also, the +grammar author has to download and install the GF software on his/her own +computer. + +

+In contrast, the +GF online editor for simple multilingual grammars +is available online, making it easier to get started. All that +is needed is a reasonably modern web browser. Even Android and iOS devices +can be used. +

+The editor +also guides the grammar author by showing a skeleton grammar file and +hinting how the parts should be filled in. When a new part is added to the +grammar, it is immediately checked for errors. + +

+Editing operations are accessed by clicking on editing symbols embedded +in the grammar display: ++=Add an item, +×=Delete an item, +%=Edit an item. +These are revealed when hovering over items. On touch devices, hovering is +in some cases simulated by tapping, but there is also a button at the bottom +of the display to "Enabile editing on touch devices" that reveals all editing +symbols. + +

+In spite of its name, the editor runs entierly in the web +browser, so once you have opened the web page, you can +continue editing grammars even while you are +offline. + +

Limitations

+ +

+At the moment, the editor supports only a small subset of the GF grammar +notation. +Proper error checking is done for abstract syntax, but not (yet) for concrete +syntax. The resource grammar library is not available. + +

+The grammars created with this editor always consists of one file for the +abstract syntax, and one file for each concrete syntax. + +

Abstract syntax

+ +The supported abstract syntax corresponds to context-free grammars +(no dependent types). The definition of an abstract syntax is limited to + + +Available editing operations: + + +Error checks: + + + +

Concrete syntax

+ +At the moment, concrete syntax definitions are limited to + + +Available editing operations: + + +Error checks: + + +

Future work

+ +This prototype gives an idea of how a web based GF grammar editor could work. +We do not expect to create a full implementation of GF that runs in the +web browser, but let the editor communicate with a server running GF. +

+By developing a GF server with an appropriate API, it should +be possible to extend the editor to support a larger fragment of GF, +to do proper error checking, and to allow grammars to be tested in the GF +shell or the minibar. +

+Grammars are currently stored locally in the browser, but a future version +could allow grammars to be stored "in the cloud", allowing the same grammars +to be accessed from multiple devices. + +


+
+ Last modified: Mon Jan 24 17:20:37 CET 2011 +
+
+TH + +
+ diff --git a/src/editor/simple/editor.css b/src/editor/simple/editor.css new file mode 100644 index 000000000..2c09897a0 --- /dev/null +++ b/src/editor/simple/editor.css @@ -0,0 +1,72 @@ +body { background: #eee; } +h1 { font-size: 175%; } +h1,h2,h3,h4,small { font-family: sans-serif; } +h1:first-child, h2:first-child { margin-top: 0; margin-bottom: 1ex; } + +#editor { max-width: 50em; } +div.grammar { border: 1px solid black; background: white; background: #9df; } +div.files { margin: 0 8px 8px 8px; } + +div#file { border: 2px solid #009; border-top-width: 0; } +pre.plain { border: 2px solid #009; } +div#file, pre.plain { background: white; padding: 0.6ex; } + +.slideshow .hidden { display: none; } + +img.right, div.right, div.modtime { float: right; } +.modtime { color: #999; white-space: nowrap; } + +div.namebar { background: #9df; } +div.namebar table { width: 100%; } +.namebar h3 { margin: 0; color: #009; } + +td.right { text-align: right; } + +.kw { font-weight: bold; font-family: sans-serif; color: #009; } +.sep { font-weight: bold; color: #009; } + +div.indent { padding-left: 1em; min-width: 1em; min-height: 1em; } + + +.more, .delete { font-weight: bold; font-family: sans-serif; } +.more, .delete, .edit { cursor: pointer; } + +.hover .more, .hover .delete, .hover .edit { visibility: hidden } + +.hover .hidden, .nohover .ifhover { display: none; } + +.editable:hover, .deletable:hover { background: #ff9; } + +.extensible:hover .more,.editable:hover > .edit ,.deletable:hover > .delete + { visibility: visible; } + +.more { color: green; } +.edit { color: orange; } +.delete { color: red; } +.error_message,.inError { color: red; } +.template, .template .sep { color: #999; } +form { display: inline-block; } + +table.tabs { + width: 100%; + border-width: 0; border-spacing: 0; empty-cells: show; +} + +table.tabs td { text-align: center; border: 2px solid #009; padding: 2px; } +table.tabs td.active { background: white; border-bottom-width: 0; } +table.tabs td.inactive { + background: #cef; + border-top-color: #66c; border-left-color: #66c; border-right-color: #66c; +} + + table.tabs td.gap +{ border-top-width: 0; border-left-width: 0; border-right-width: 0; } + +table.tabs input[type=button] { + border: 0; + background: inherit; + color: #009; + font-size: inherit; + font-weight: bold; + /*text-decoration: underline;*/ +} \ No newline at end of file diff --git a/src/editor/simple/editor.js b/src/editor/simple/editor.js new file mode 100644 index 000000000..6420b5eac --- /dev/null +++ b/src/editor/simple/editor.js @@ -0,0 +1,789 @@ + + +var editor=element("editor"); + +/* -------------------------------------------------------------------------- */ +function div_id(id,cs) { return node("div",{id:id},cs); } +function div_class(cls,cs) { return node("div",{"class":cls},cs); } +function a(url,linked) { return node("a",{href:url},linked); } +function ul(lis) { return node("ul",{},lis); } +function li(xs) { return node("li",{},xs); } +function table(rows) { return node("table",{},rows); } +function td_right(cs) { return node("td",{"class":"right"},cs); } +function jsurl(js) { return "javascript:"+js; } + +function insertAfter(el,ref) { + ref.parentNode.insertBefore(el,ref.nextSibling); +} +/* -------------------------------------------------------------------------- */ + +function initial_view() { + var current=local.get("current"); + if(current>0) open_grammar(current-1); + else draw_grammar_list(); + //debug(local.get("dir","no server directory yet")); +} + +function draw_grammar_list() { + local.put("current",0); + editor.innerHTML=""; + editor.appendChild(node("h3",{},[text("Your grammars")])); + var gs=ul([]); + function del(i) { return function () { delete_grammar(i); } } + for(var i=0;i0 && !local.get(local.count-1)) + local.count--; + initial_view(); + } +} + +function open_grammar(i) { + var g=local.get(i); + g.index=i; + local.put("current",i+1); + edit_grammar(g); +} + +function close_grammar(g) { save_grammar(g); draw_grammar_list(); } +function reload_grammar(g) { save_grammar(g); edit_grammar(g); } + +function save_grammar(g) { + if(g.index==null) g.index=local.count++; + local.put(g.index,g); +} + +function edit_grammar(g) { + editor.innerHTML=""; + editor.appendChild(draw_grammar(g)); +} + + +function draw_grammar(g) { + var files=div_class("files",[draw_filebar(g),draw_file(g)]); + return div_class("grammar",[draw_namebar(g,files),files]) + +} + +function draw_namebar(g,files) { + return div_class("namebar", + [table([tr([td(draw_name(g)), + td_right([draw_plainbutton(g,files), + upload_button(g), + draw_closebutton(g)])])])]) +} + +function draw_name(g) { + return editable("h3",text(g.basename),g,edit_name,"Rename grammar"); +} + +function draw_closebutton(g) { + var b=button("X",function(){close_grammar(g);}); + b.title="Save and Close this grammar"; + return b; +} + +function draw_plainbutton(g,files) { + var b2; + function show_editor() { edit_grammar(g); } + function show_plain() { + files.innerHTML="
"+show_grammar(g)+"
" + b.style.display="none"; + if(b2) b2.style.display=""; + else { + b2=button("Show editor",show_editor); + insertAfter(b2,b); + } + } + var b=button("Show plain",show_plain); + b.title="Show plain text representaiton of the grammar"; + return b; +} + +function upload_button(g) { + var b=button("Upload",function(){upload(g);}); + b.title="Upload the grammar to the server to check it in GF and test it in the minibar"; + return b; +} + +function lang(code,name) { return { code:code, name:name} } +function lang1(name) { + var ws=name.split("/"); + return ws.length==1 ? lang(name.substr(0,3),name) : lang(ws[0],ws[1]); +} +var languages = + map(lang1,"Amharic Arabic Bulgarian Catalan Danish Dutch English Finnish French German Hindi Ina/Interlingua Italian Latin Norwegian Polish Ron/Romanian Russian Spanish Swedish Thai Turkish Urdu".split(" ")); +//languages.push(lang("Other","Other")); + +var langname={}; +//for(var i=0;i=ci) g.current--; + reload_grammar(g); + } +} + +function draw_filebar(g) { + var cur=(g.current||0)-1; + var filebar = empty_class("tr","extensible") + filebar.appendChild(gap()); + filebar.appendChild( + tab(cur== -1,button("Abstract",function(){open_abstract(g);}))); + var cs=g.concretes; + function del(ci) { return function() { delete_concrete(g,ci); }} + function open_conc(i) { return function() {open_concrete(g,1*i); }} +// for(var i=0;i0 // && g.current<=g.concretes.length + ? draw_concrete(g,g.current-1) + : draw_abstract(g); +} + +function draw_startcat(g) { + var abs=g.abstract; + var startcat = abs.startcat || abs.cats[0]; + function opt(cat) { return option(cat,cat); } + var m= node("select",{},map(opt,abs.cats)); + m.value=startcat; + m.onchange=function() { abs.startcat=m.value; save_grammar(g); } + return indent([kw("flags startcat"),sep(" = "),m]); +} + +function draw_abstract(g) { + var kw_cat = kw("cat"); + kw_cat.title = "The categories (nonterminals) of the grammar are enumerated here."; + var kw_fun = kw("fun"); + kw_fun.title = "The functions (productions) of the grammar are enumerated here."; + var flags=g.abstract.startcat || g.abstract.cats.length>1 + ? draw_startcat(g) + : text(""); + return div_id("file", + [kw("abstract "),ident(g.basename),sep(" = "), + flags, + indent([extensible([kw_cat, + indent(draw_cats(g))]), + extensible([kw_fun, + indent(draw_funs(g))])])]); +} + +function add_cat(g,el) { + function add(s) { + var cats=s.split(/\s*(?:\s|[;])\s*/); // allow separating spaces or ";" + if(cats.length>0 && cats[cats.length-1]=="") cats.pop(); + for(var i in cats) { + var err=check_name(cats[i],"Category"); + if(err) return err; + } + for(var i in cats) g.abstract.cats.push(cats[i]); + reload_grammar(g); + return null; + } + string_editor(el,"",add); +} + +function delete_ix(old,ix) { + var a=[]; +// for(var i=0;i0) el.appendChild(sep(" → ")); + el.appendChild(check(t[i],ident(t[i]))); + } + return el; +} + +function edit_name(g,el) { + function change_name(name) { + if(name!=g.basename && name!="") { + var err=check_name(name,"Grammar"); + if(err) return err; + g.basename=name + reload_grammar(g); + } + return null; + } + string_editor(el,g.basename,change_name) +} +/* -------------------------------------------------------------------------- */ + +function draw_concrete(g,i) { + var conc=g.concretes[i]; + return div_id("file", + [kw("concrete "),ident(g.basename+conc.langcode), + kw(" of "),ident(g.basename),sep(" = "), + indent([extensible([kw("param"),draw_params(g,i)])]), + indent([kw("lincat"),draw_lincats(g,i)]), + indent([extensible([kw("oper"),draw_opers(g,i)])]), + indent([kw("lin"),draw_lins(g,i)]) + ]) +} + + +function draw_param(p,dp) { + function check(el) { + return ifError(dp[p.name],"Same parameter type defined twice",el); + } + return node("span",{},[check(ident(p.name)),sep(" = "),text(p.rhs)]); +} + +function add_param(g,ci,el) { + function add(s) { + var p=parse_param(s); + if(p.ok) { + g.concretes[ci].params.push(p.ok); + reload_grammar(g); + return null; + } + else + return p.error + } + string_editor(el,"",add); +} + +function edit_param(ci,i) { + return function (g,el) { + function replace(s) { + var p=parse_param(s); + if(p.ok) { + g.concretes[ci].params[i]=p.ok; + reload_grammar(g); + return null; + } + else + return p.error; + } + string_editor(el,show_param(g.concretes[ci].params[i]),replace); + } +} + + +function delete_param(g,ci,ix) { + with(g.concretes[ci]) params=delete_ix(params,ix); + reload_grammar(g); +} + +function draw_params(g,ci) { + var conc=g.concretes[ci]; + conc.params || (conc.params=[]); + var params=conc.params; + var es=[]; + var dp={}; + function del(i) { return function() { delete_param(g,ci,i); }} + function draw_eparam(i,dp) { + return editable("span",draw_param(params[i],dp),g,edit_param(ci,i),"Edit this parameter type"); + } + for(var i in params) { + es.push(div_class("param",[deletable(del(i),draw_eparam(i,dp),"Delete this parameter type")])); + dp[params[i].name]=true; + } + es.push(more(g,function(g,el) { return add_param(g,ci,el)}, + "Add a new parameter type")); + return indent(es); +} + +function delete_lincat(g,ci,cat) { + var i; + var c=g.concretes[ci]; + for(i=0;i1 ? n+(++use[n]) : n; + } + return map(unique,names); +} + +function draw_lins(g,i) { + var conc=g.concretes[i]; + function edit(f) { + return function(g,el) { + function ok(s) { + if(f.template) + conc.lins.push({fun:f.fun,args:f.args,lin:s}); + else f.lin=s; + reload_grammar(g); + return null; + } + string_editor(el,f.lin,ok) + } + } + function del(fun) { return function () { delete_lin(g,i,fun); } } + function dl(f,cls) { + var l=[ident(f.fun)] + for(var i in f.args) { + l.push(text(" ")); + l.push(ident(f.args[i])); + } + l.push(sep(" = ")); + var t=editable("span",text(f.lin),g,edit(f),"Edit lin for "+f.fun); + l.push(t); + return node("span",{"class":cls},l); + } + var df=defined_funs(g); + function draw_lin(f) { + var fun=f.fun; + var err= !df[fun]; + var l= err ? deletable(del(fun),dl(f,"lin"),"Delete this function") : dl(f,"lin") + var l=ifError(err,"Function "+fun+" is not part of the abstract syntax",l); + delete df[fun]; + return wrap("div",l); + } + function largs(f) { + var funs=g.abstract.funs; + for(var i=0;i10) i.size=init.length+5; + var e=node("form",{}, + [i, + node("input",{type:"submit",value:"OK"},[]), + button("Cancel",restore), + text(" "), + m]) + e.onsubmit=done + start(""); +} + +function ifError(b,msg,el) { return b ? inError(msg,el) : el; } + +function inError(msg,el) { + return node("span",{"class":"inError",title:msg},[el]); +} + +function kw(txt) { return wrap_class("span","kw",text(txt)); } +function sep(txt) { return wrap_class("span","sep",text(txt)); } +function ident(txt) { return wrap_class("span","ident",text(txt)); } +function indent(cs) { return div_class("indent",cs); } + +function extensible(cs) { return div_class("extensible",cs); } + +function more(g,action,hint) { + var b=node("span",{"class":"more","title":hint || "Add more"}, + [text(" + ")]); + b.onclick=function() { action(g,b); } + return b; +} + +function editable(tag,cs,g,f,hint) { + var b=edit_button(function(){f(g,e)},hint); + var e=node(tag,{"class":"editable"},[cs,b]); + return e; +} + +function edit_button(action,hint) { + var b=node("span",{"class":"edit","title":hint || "Edit"},[text("%")]); + b.onclick=action; + return b; +} + +function deletable(del,el,hint) { + var b=node("span",{"class":"delete",title:hint || "Delete"},[text("×")]) + b.onclick=del; + return node("span",{"class":"deletable"},[b,el]) +} + +function touch_edit() { + var b=node("input",{type:"checkbox"},[]); + function touch() { + document.body.className=b.checked ? "nohover" : "hover"; + } + b.onchange=touch; + insertAfter(b,editor); + insertAfter(wrap("small",text("Enable editing on touch devices. ")),b); + +} + +/* --- Initialization ------------------------------------------------------- */ + +//document.body.appendChild(empty_id("div","debug")); + +initial_view(); +touch_edit(); diff --git a/src/editor/simple/gf_abs.js b/src/editor/simple/gf_abs.js new file mode 100644 index 000000000..468ed91c7 --- /dev/null +++ b/src/editor/simple/gf_abs.js @@ -0,0 +1 @@ +/* Abstract syntax for a small subset of GF grammars in JavaScript */ diff --git a/src/editor/simple/gfse.manifest b/src/editor/simple/gfse.manifest new file mode 100644 index 000000000..60c6f3757 --- /dev/null +++ b/src/editor/simple/gfse.manifest @@ -0,0 +1,4 @@ +CACHE MANIFEST +# 5 +NETWORK: +* diff --git a/src/editor/simple/index.html b/src/editor/simple/index.html new file mode 100644 index 000000000..ef3843379 --- /dev/null +++ b/src/editor/simple/index.html @@ -0,0 +1,42 @@ + + + +GF online editor for simple multilingual grammars + + + + + + + + + + + + +

GF online editor for simple multilingual grammars

+
+
+ +Hover over items for hints and editing options. + + + +
+
+HTML + Last modified: Wed Feb 16 15:10:52 CET 2011 +
+About + + + + + + diff --git a/src/editor/simple/localstorage.js b/src/editor/simple/localstorage.js new file mode 100644 index 000000000..31201998c --- /dev/null +++ b/src/editor/simple/localstorage.js @@ -0,0 +1,21 @@ + +// We use localStorage to store the grammars, +// see http://diveintohtml5.org/storage.html + +var local={ + prefix:"gf.editor.simple.grammar", + get: function (name,def) { + var id=this.prefix+name + return localStorage[id] ? JSON.parse(localStorage[id]) : def; + }, + put: function (name,value) { + var id=this.prefix+name; + localStorage[id]=JSON.stringify(value); + }, + remove: function(name) { + var id=this.prefix+name; + localStorage.removeItem(id); + }, + get count() { return this.get("count",0); }, + set count(v) { this.put("count",v); } +} diff --git a/src/editor/simple/save.hs b/src/editor/simple/save.hs new file mode 100644 index 000000000..01d3ce270 --- /dev/null +++ b/src/editor/simple/save.hs @@ -0,0 +1,12 @@ +import Monad(zipWithM_) +import System(getArgs) + +main = save =<< getArgs + +save [dir] = + do fs@[ns,_] <- readIO =<< getContents + save_all fs + putStrLn $ unwords [n++".gf"|n<-ns] + where + save_all [ns,cs] = zipWithM_ write1 ns cs + write1 n = writeFile (dir++"/"++n++".gf") diff --git a/src/editor/simple/slideshow.js b/src/editor/simple/slideshow.js new file mode 100644 index 000000000..307bcd98f --- /dev/null +++ b/src/editor/simple/slideshow.js @@ -0,0 +1,86 @@ + +var internet_explorer=navigator.appName=="Microsoft Internet Explorer"; + +/* How to change opacity in IE: +http://joseph.randomnetworks.com/archives/2006/08/16/css-opacity-in-internet-explorer-ie/ +*/ + +var set_opacity = + internet_explorer + ? function(el,o) { el.style.filter="alpha(opacity="+Math.round(o*100)+")";} + : function(el,o) { el.style.opacity=o; }; + +function start_slideshow(img,options) { + var p=img.parentNode; + if(p.tagName=="A") p=p.parentNode; + var is=p.getElementsByTagName("img"); + if(is.length>1) { + var cur=0; + var w=img.width; + var h=img.height; + //p.style.position="relative"; + p.style.minWidth=w+"px"; + p.style.minHeight=h+"px"; + var images=[]; + for(var i=0;iw) { w=n.width; p.style.minWidth=w+"px"; } + if(n.height>h) { h=n.height; p.style.minHeight=h+"px"; } + c.style.position="absolute"; + c.style.zIndex=0; + fade(n,0,1,ft,function() { + if(c.width>n.width || c.height>n.height) fade(c,1,0,ft,null); + else set_opacity(c,0); }); + //debug.innerHTML=w+"x"+h; + //for(var i=0;i=1) { next=1; done(); } + if(next<=0) { next=0; done(); } + set_opacity(el,next); + el.current=next + } + if(!el.timer) { + el.current=start; + el.timer=setInterval(f,dt); + } +} diff --git a/src/editor/simple/upload.cgi b/src/editor/simple/upload.cgi new file mode 100644 index 000000000..fbc5a0be1 --- /dev/null +++ b/src/editor/simple/upload.cgi @@ -0,0 +1,77 @@ +#!/bin/bash + +bin=/Users/hallgren/www/bin + +charset="UTF-8" +AUTOHEADER=no + +. $bin/cgistart.sh +PATH=$PATH:/Users/hallgren/.cabal/bin +export LC_CTYPE="UTF-8" +style_url="editor.css" + +make_dir() { + dir="$(mktemp -d ../tmp/gfse.XXXXXXXX)" +# chmod a+rxw "$dir" + chmod a+rx "$dir" + ln "$dir/../../grammars/grammars.cgi" "$dir" +} + + +check_grammar() { + pagestart "Uploaded" +# echo "$PATH_INFO" + files=$(Reg from-url | LC_CTYPE=sv_SE.ISO8859-1 ./save "$dir") + cd $dir + begin pre + if gf -s -make $files 2>&1 ; then + end + h3 OK + begin ul + [ -z "$minibar" ] || { li; link "$minibar?/tmp/${dir##*/}/" "Minibar"; } + [ -z "$gfshell" ] || { li; link "$gfshell?dir=${dir##*/}" "GF Shell"; } + end + begin pre + ls -l *.pgf + else + end + begin h3 class=error_message; echo Error; end + for f in *.gf ; do + h4 "$f" + begin pre class=plain + cat -n "$f" + end + done + fi + hr + date +# begin pre ; env + endall +} + +case "$REQUEST_METHOD" in + POST) + case "$PATH_INFO" in + /tmp/gfse.*) + style_url="../../$style_url" + dir="../tmp/${PATH_INFO##*/}" + check_grammar + ;; + *) + make_dir + check_grammar + rm -rf "$dir" + esac + ;; + GET) + case "$QUERY_STRING" in + dir) make_dir + ContentType="text/plain" + cgiheaders + echo "/tmp/${dir##*/}" + ;; + *) pagestart "Error" + echo "What do you want?" + endall + esac +esac