Commit ff12f2ef authored by Rob Pike's avatar Rob Pike

add (stub) parser to template code, enabling rewrite.

update pretty to use it.
change stdout to stderr in pretty.

R=rsc
DELTA=173  (52 added, 24 deleted, 97 changed)
OCL=27405
CL=27409
parent c1ed7d7d
...@@ -18,7 +18,7 @@ func HtmlFormatter(w io.Write, value interface{}, format string) { ...@@ -18,7 +18,7 @@ func HtmlFormatter(w io.Write, value interface{}, format string) {
fmt.Fprint(w, value); fmt.Fprint(w, value);
} }
// StringFormatter formats returns the default string representation. // StringFormatter formats into the default string representation.
// It is stored under the name "str" and is the default formatter. // It is stored under the name "str" and is the default formatter.
// You can override the default formatter by storing your default // You can override the default formatter by storing your default
// under the name "" in your custom formatter map. // under the name "" in your custom formatter map.
......
...@@ -57,52 +57,53 @@ var builtins = FormatterMap { ...@@ -57,52 +57,53 @@ var builtins = FormatterMap {
"" : StringFormatter, "" : StringFormatter,
} }
type template struct { // State for executing a Template
type state struct {
parent *state; // parent in hierarchy
errorchan chan *os.Error; // for erroring out errorchan chan *os.Error; // for erroring out
linenum *int; // shared by all templates derived from this one
parent *template;
data reflect.Value; // the driver data for this section etc. data reflect.Value; // the driver data for this section etc.
wr io.Write; // where to send output
}
// Report error and stop generation.
func (st *state) error(err *os.Error, args ...) {
st.errorchan <- err;
sys.Goexit();
}
type Template struct {
fmap FormatterMap; // formatters for variables fmap FormatterMap; // formatters for variables
buf []byte; // input text to process buf []byte; // input text to process
p int; // position in buf p int; // position in buf
wr io.Write; // where to send output linenum *int; // position in input
} }
// Create a top-level template // Create a top-level template
func newTemplate(ch chan *os.Error, linenum *int, buf []byte, data reflect.Value, fmap FormatterMap, wr io.Write) *template { func newTemplate(buf []byte, fmap FormatterMap) *Template {
t := new(template); t := new(Template);
t.errorchan = ch;
t.linenum = linenum;
*linenum = 1;
t.parent = nil;
t.data = data;
t.buf = buf; t.buf = buf;
t.p = 0; t.p = 0;
t.fmap = fmap; t.fmap = fmap;
t.wr = wr; t.linenum = new(int);
return t; return t;
} }
// Create a template deriving from its parent // Create a template deriving from its parent
func childTemplate(parent *template, buf []byte, data reflect.Value) *template { func childTemplate(parent *Template, buf []byte) *Template {
t := newTemplate(parent.errorchan, parent.linenum, buf, data, parent.fmap, parent.wr); t := new(Template);
t.parent = parent; t.buf = buf;
t.p = 0;
t.fmap = parent.fmap;
t.linenum = parent.linenum;
return t; return t;
} }
// Report error and stop generation.
func (t *template) error(err *os.Error, args ...) {
fmt.Fprintf(os.Stderr, "template error: line %d: %s%s\n", *t.linenum, err, fmt.Sprint(args)); // TODO: drop this? (only way to get line number)
t.errorchan <- err;
sys.Goexit();
}
func white(c uint8) bool { func white(c uint8) bool {
return c == ' ' || c == '\t' || c == '\r' || c == '\n' return c == ' ' || c == '\t' || c == '\r' || c == '\n'
} }
func (t *template) execute() func (t *Template) execute(st *state)
func (t *template) executeSection(w []string) func (t *Template) executeSection(w []string, st *state)
// nextItem returns the next item from the input buffer. If the returned // nextItem returns the next item from the input buffer. If the returned
// item is empty, we are at EOF. The item will be either a brace- // item is empty, we are at EOF. The item will be either a brace-
...@@ -110,7 +111,7 @@ func (t *template) executeSection(w []string) ...@@ -110,7 +111,7 @@ func (t *template) executeSection(w []string)
// strings. Most tokens stop at (but include, if plain text) a newline. // strings. Most tokens stop at (but include, if plain text) a newline.
// Action tokens on a line by themselves drop the white space on // Action tokens on a line by themselves drop the white space on
// either side, up to and including the newline. // either side, up to and including the newline.
func (t *template) nextItem() []byte { func (t *Template) nextItem(st *state) []byte {
brace := false; // are we waiting for an opening brace? brace := false; // are we waiting for an opening brace?
special := false; // is this a {.foo} directive, which means trim white space? special := false; // is this a {.foo} directive, which means trim white space?
// Delete surrounding white space if this {.foo} is the only thing on the line. // Delete surrounding white space if this {.foo} is the only thing on the line.
...@@ -129,7 +130,7 @@ Loop: ...@@ -129,7 +130,7 @@ Loop:
// white space, do nothing // white space, do nothing
case '{': case '{':
if brace { if brace {
t.error(ErrLBrace) st.error(ErrLBrace)
} }
// anything interesting already on the line? // anything interesting already on the line?
if !only_white { if !only_white {
...@@ -147,7 +148,7 @@ Loop: ...@@ -147,7 +148,7 @@ Loop:
brace = true; brace = true;
case '}': case '}':
if !brace { if !brace {
t.error(ErrUnmatchedRBrace) st.error(ErrUnmatchedRBrace)
} }
brace = false; brace = false;
i++; i++;
...@@ -157,7 +158,7 @@ Loop: ...@@ -157,7 +158,7 @@ Loop:
} }
} }
if brace { if brace {
t.error(ErrUnmatchedLBrace) st.error(ErrUnmatchedLBrace)
} }
item := t.buf[start:i]; item := t.buf[start:i];
if special && trim_white { if special && trim_white {
...@@ -204,17 +205,17 @@ func words(buf []byte) []string { ...@@ -204,17 +205,17 @@ func words(buf []byte) []string {
// Analyze an item and return its type and, if it's an action item, an array of // Analyze an item and return its type and, if it's an action item, an array of
// its constituent words. // its constituent words.
func (t *template) analyze(item []byte) (tok int, w []string) { func (t *Template) analyze(item []byte, st *state) (tok int, w []string) {
// item is known to be non-empty // item is known to be non-empty
if item[0] != '{' { if item[0] != '{' {
tok = Text; tok = Text;
return return
} }
if item[len(item)-1] != '}' { if item[len(item)-1] != '}' {
t.error(ErrUnmatchedLBrace) // should not happen anyway st.error(ErrUnmatchedLBrace) // should not happen anyway
} }
if len(item) <= 2 { if len(item) <= 2 {
t.error(ErrEmptyDirective) st.error(ErrEmptyDirective)
} }
// Comment // Comment
if item[1] == '#' { if item[1] == '#' {
...@@ -224,10 +225,10 @@ func (t *template) analyze(item []byte) (tok int, w []string) { ...@@ -224,10 +225,10 @@ func (t *template) analyze(item []byte) (tok int, w []string) {
// Split into words // Split into words
w = words(item[1: len(item)-1]); // drop final brace w = words(item[1: len(item)-1]); // drop final brace
if len(w) == 0 { if len(w) == 0 {
t.error(ErrBadDirective) st.error(ErrBadDirective)
} }
if len(w[0]) == 0 { if len(w[0]) == 0 {
t.error(ErrEmptyDirective) st.error(ErrEmptyDirective)
} }
if len(w) == 1 && w[0][0] != '.' { if len(w) == 1 && w[0][0] != '.' {
tok = Variable; tok = Variable;
...@@ -245,30 +246,30 @@ func (t *template) analyze(item []byte) (tok int, w []string) { ...@@ -245,30 +246,30 @@ func (t *template) analyze(item []byte) (tok int, w []string) {
return; return;
case ".section": case ".section":
if len(w) != 2 { if len(w) != 2 {
t.error(ErrFields, ": ", string(item)) st.error(ErrFields, ": ", string(item))
} }
tok = Section; tok = Section;
return; return;
case ".repeated": case ".repeated":
if len(w) != 3 || w[1] != "section" { if len(w) != 3 || w[1] != "section" {
t.error(ErrFields, ": ", string(item)) st.error(ErrFields, ": ", string(item))
} }
tok = Repeated; tok = Repeated;
return; return;
case ".alternates": case ".alternates":
if len(w) != 2 || w[1] != "with" { if len(w) != 2 || w[1] != "with" {
t.error(ErrFields, ": ", string(item)) st.error(ErrFields, ": ", string(item))
} }
tok = Alternates; tok = Alternates;
return; return;
} }
t.error(ErrBadDirective, ": ", string(item)); st.error(ErrBadDirective, ": ", string(item));
return return
} }
// If the data for this template is a struct, find the named variable. // If the data for this template is a struct, find the named variable.
func (t *template) findVar(s string) (int, int) { func (st *state) findVar(s string) (int, int) {
typ, ok := t.data.Type().(reflect.StructType); typ, ok := st.data.Type().(reflect.StructType);
if ok { if ok {
for i := 0; i < typ.Len(); i++ { for i := 0; i < typ.Len(); i++ {
name, ftyp, tag, offset := typ.Field(i); name, ftyp, tag, offset := typ.Field(i);
...@@ -296,25 +297,25 @@ func empty(v reflect.Value, indirect_ok bool) bool { ...@@ -296,25 +297,25 @@ func empty(v reflect.Value, indirect_ok bool) bool {
} }
// Execute a ".repeated" section // Execute a ".repeated" section
func (t *template) executeRepeated(w []string) { func (t *Template) executeRepeated(w []string, st *state) {
if w[1] != "section" { if w[1] != "section" {
t.error(ErrSyntax, `: .repeated must have "section"`) st.error(ErrSyntax, `: .repeated must have "section"`)
} }
// Find driver array/struct for this section. It must be in the current struct. // Find driver array/struct for this section. It must be in the current struct.
// The special name "@" leaves us at this level. // The special name "@" leaves us at this level.
var field reflect.Value; var field reflect.Value;
if w[2] == "@" { if w[2] == "@" {
field = t.data field = st.data
} else { } else {
i, kind := t.findVar(w[1]); i, kind := st.findVar(w[1]);
if i < 0 { if i < 0 {
t.error(ErrNoVar, ": ", w[2]); st.error(ErrNoVar, ": ", w[2]);
} }
field = reflect.Indirect(t.data.(reflect.StructValue).Field(i)); field = reflect.Indirect(st.data.(reflect.StructValue).Field(i));
} }
// Must be an array/slice // Must be an array/slice
if field != nil && field.Kind() != reflect.ArrayKind { if field != nil && field.Kind() != reflect.ArrayKind {
t.error(ErrBadType, " in .repeated: ", w[2], " ", field.Type().String()); st.error(ErrBadType, " in .repeated: ", w[2], " ", field.Type().String());
} }
// Scan repeated section, remembering slice of text we must execute. // Scan repeated section, remembering slice of text we must execute.
nesting := 0; nesting := 0;
...@@ -322,11 +323,11 @@ func (t *template) executeRepeated(w []string) { ...@@ -322,11 +323,11 @@ func (t *template) executeRepeated(w []string) {
end := t.p; end := t.p;
Loop: Loop:
for { for {
item := t.nextItem(); item := t.nextItem(st);
if len(item) == 0 { if len(item) == 0 {
t.error(ErrNoEnd) st.error(ErrNoEnd)
} }
tok, s := t.analyze(item); tok, s := t.analyze(item, st);
switch tok { switch tok {
case Comment: case Comment:
continue; // just ignore it continue; // just ignore it
...@@ -347,26 +348,25 @@ Loop: ...@@ -347,26 +348,25 @@ Loop:
if field != nil { if field != nil {
array := field.(reflect.ArrayValue); array := field.(reflect.ArrayValue);
for i := 0; i < array.Len(); i++ { for i := 0; i < array.Len(); i++ {
elem := reflect.Indirect(array.Elem(i)); tmp := childTemplate(t, t.buf[start:end]);
tmp := childTemplate(t, t.buf[start:end], elem); tmp.execute(&state{st, st.errorchan, reflect.Indirect(array.Elem(i)), st.wr});
tmp.execute();
} }
} }
} }
// Execute a ".section" // Execute a ".section"
func (t *template) executeSection(w []string) { func (t *Template) executeSection(w []string, st *state) {
// Find driver array/struct for this section. It must be in the current struct. // Find driver array/struct for this section. It must be in the current struct.
// The special name "@" leaves us at this level. // The special name "@" leaves us at this level.
var field reflect.Value; var field reflect.Value;
if w[1] == "@" { if w[1] == "@" {
field = t.data field = st.data
} else { } else {
i, kind := t.findVar(w[1]); i, kind := st.findVar(w[1]);
if i < 0 { if i < 0 {
t.error(ErrNoVar, ": ", w[1]); st.error(ErrNoVar, ": ", w[1]);
} }
field = t.data.(reflect.StructValue).Field(i); field = st.data.(reflect.StructValue).Field(i);
} }
// Scan section, remembering slice of text we must execute. // Scan section, remembering slice of text we must execute.
orFound := false; orFound := false;
...@@ -376,11 +376,11 @@ func (t *template) executeSection(w []string) { ...@@ -376,11 +376,11 @@ func (t *template) executeSection(w []string) {
accumulate := !empty(field, true); // Keep this section if there's data accumulate := !empty(field, true); // Keep this section if there's data
Loop: Loop:
for { for {
item := t.nextItem(); item := t.nextItem(st);
if len(item) == 0 { if len(item) == 0 {
t.error(ErrNoEnd) st.error(ErrNoEnd)
} }
tok, s := t.analyze(item); tok, s := t.analyze(item, st);
switch tok { switch tok {
case Comment: case Comment:
continue; // just ignore it continue; // just ignore it
...@@ -394,7 +394,7 @@ Loop: ...@@ -394,7 +394,7 @@ Loop:
break break
} }
if orFound { if orFound {
t.error(ErrSyntax, ": .or"); st.error(ErrSyntax, ": .or");
} }
orFound = true; orFound = true;
if !accumulate { if !accumulate {
...@@ -418,25 +418,25 @@ Loop: ...@@ -418,25 +418,25 @@ Loop:
end = t.p end = t.p
} }
} }
tmp := childTemplate(t, t.buf[start:end], field); tmp := childTemplate(t, t.buf[start:end]);
tmp.execute(); tmp.execute(&state{st, st.errorchan, field, st.wr});
} }
// Look up a variable, up through the parent if necessary. // Look up a variable, up through the parent if necessary.
func (t *template) varValue(name string) reflect.Value { func (t *Template) varValue(name string, st *state) reflect.Value {
i, kind := t.findVar(name); i, kind := st.findVar(name);
if i < 0 { if i < 0 {
if t.parent == nil { if st.parent == nil {
t.error(ErrNoVar, ": ", name) st.error(ErrNoVar, ": ", name)
} }
return t.parent.varValue(name); return t.varValue(name, st.parent);
} }
return t.data.(reflect.StructValue).Field(i); return st.data.(reflect.StructValue).Field(i);
} }
// Evalute a variable, looking up through the parent if necessary. // Evaluate a variable, looking up through the parent if necessary.
// If it has a formatter attached ({var|formatter}) run that too. // If it has a formatter attached ({var|formatter}) run that too.
func (t *template) writeVariable(w io.Write, name_formatter string) { func (t *Template) writeVariable(st *state, name_formatter string) {
name := name_formatter; name := name_formatter;
formatter := ""; formatter := "";
bar := strings.Index(name_formatter, "|"); bar := strings.Index(name_formatter, "|");
...@@ -444,61 +444,79 @@ func (t *template) writeVariable(w io.Write, name_formatter string) { ...@@ -444,61 +444,79 @@ func (t *template) writeVariable(w io.Write, name_formatter string) {
name = name_formatter[0:bar]; name = name_formatter[0:bar];
formatter = name_formatter[bar+1:len(name_formatter)]; formatter = name_formatter[bar+1:len(name_formatter)];
} }
val := t.varValue(name).Interface(); val := t.varValue(name, st).Interface();
// is it in user-supplied map? // is it in user-supplied map?
if t.fmap != nil { if t.fmap != nil {
if fn, ok := t.fmap[formatter]; ok { if fn, ok := t.fmap[formatter]; ok {
fn(w, val, formatter); fn(st.wr, val, formatter);
return; return;
} }
} }
// is it in builtin map? // is it in builtin map?
if fn, ok := builtins[formatter]; ok { if fn, ok := builtins[formatter]; ok {
fn(w, val, formatter); fn(st.wr, val, formatter);
return; return;
} }
t.error(ErrNoFormatter, ": ", formatter); st.error(ErrNoFormatter, ": ", formatter);
panic("notreached"); panic("notreached");
} }
func (t *template) execute() { func (t *Template) execute(st *state) {
for { for {
item := t.nextItem(); item := t.nextItem(st);
if len(item) == 0 { if len(item) == 0 {
return return
} }
tok, w := t.analyze(item); tok, w := t.analyze(item, st);
switch tok { switch tok {
case Comment: case Comment:
break; break;
case Text: case Text:
t.wr.Write(item); st.wr.Write(item);
case Literal: case Literal:
switch w[0] { switch w[0] {
case ".meta-left": case ".meta-left":
t.wr.Write(lbrace); st.wr.Write(lbrace);
case ".meta-right": case ".meta-right":
t.wr.Write(rbrace); st.wr.Write(rbrace);
case ".space": case ".space":
t.wr.Write(space); st.wr.Write(space);
default: default:
panic("unknown literal: ", w[0]); panic("unknown literal: ", w[0]);
} }
case Variable: case Variable:
t.writeVariable(t.wr, w[0]); t.writeVariable(st, w[0]);
case Or, End, Alternates: case Or, End, Alternates:
t.error(ErrSyntax, ": ", string(item)); st.error(ErrSyntax, ": ", string(item));
case Section: case Section:
t.executeSection(w); t.executeSection(w, st);
case Repeated: case Repeated:
t.executeRepeated(w); t.executeRepeated(w, st);
default: default:
panic("bad directive in execute:", string(item)); panic("bad directive in execute:", string(item));
} }
} }
} }
func Execute(s string, data interface{}, fmap FormatterMap, wr io.Write) *os.Error { func (t *Template) parse() {
// stub for now
}
func Parse(s string, fmap FormatterMap) (*Template, *os.Error, int) {
ch := make(chan *os.Error);
t := newTemplate(io.StringBytes(s), fmap);
go func() {
t.parse();
ch <- nil; // clean return;
}();
err := <-ch;
if err != nil {
return nil, err, *t.linenum
}
return t, nil, 0
}
func (t *Template) Execute(data interface{}, wr io.Write) *os.Error {
// Extract the driver struct. // Extract the driver struct.
val := reflect.Indirect(reflect.NewValue(data)); val := reflect.Indirect(reflect.NewValue(data));
sval, ok1 := val.(reflect.StructValue); sval, ok1 := val.(reflect.StructValue);
...@@ -506,10 +524,8 @@ func Execute(s string, data interface{}, fmap FormatterMap, wr io.Write) *os.Err ...@@ -506,10 +524,8 @@ func Execute(s string, data interface{}, fmap FormatterMap, wr io.Write) *os.Err
return ErrNotStruct return ErrNotStruct
} }
ch := make(chan *os.Error); ch := make(chan *os.Error);
var linenum int;
t := newTemplate(ch, &linenum, io.StringBytes(s), val, fmap, wr);
go func() { go func() {
t.execute(); t.execute(&state{nil, ch, val, wr});
ch <- nil; // clean return; ch <- nil; // clean return;
}(); }();
return <-ch; return <-ch;
......
...@@ -133,6 +133,11 @@ var tests = []*Test { ...@@ -133,6 +133,11 @@ var tests = []*Test {
"Header=77\n" "Header=77\n"
"Header=77\n" "Header=77\n"
}, },
&Test{
"{.section data}{.end} {header}\n",
" Header\n"
},
// Repeated // Repeated
&Test{ &Test{
...@@ -157,12 +162,6 @@ var tests = []*Test { ...@@ -157,12 +162,6 @@ var tests = []*Test {
"Header=77\n" "Header=77\n"
}, },
// Bugs
&Test{
"{.section data}{.end} {integer}\n",
" 77\n"
},
} }
func TestAll(t *testing.T) { func TestAll(t *testing.T) {
...@@ -178,9 +177,14 @@ func TestAll(t *testing.T) { ...@@ -178,9 +177,14 @@ func TestAll(t *testing.T) {
var buf io.ByteBuffer; var buf io.ByteBuffer;
for i, test := range tests { for i, test := range tests {
buf.Reset(); buf.Reset();
err := Execute(test.in, s, formatters, &buf); tmpl, err, line := Parse(test.in, formatters);
if err != nil {
t.Error("unexpected parse error:", err, "line", line);
continue;
}
err = tmpl.Execute(s, &buf);
if err != nil { if err != nil {
t.Error("unexpected error:", err) t.Error("unexpected execute error:", err)
} }
if string(buf.Data()) != test.out { if string(buf.Data()) != test.out {
t.Errorf("for %q: expected %q got %q", test.in, test.out, string(buf.Data())); t.Errorf("for %q: expected %q got %q", test.in, test.out, string(buf.Data()));
...@@ -189,9 +193,12 @@ func TestAll(t *testing.T) { ...@@ -189,9 +193,12 @@ func TestAll(t *testing.T) {
} }
func TestBadDriverType(t *testing.T) { func TestBadDriverType(t *testing.T) {
err := Execute("hi", "hello", nil, os.Stdout); tmpl, err, line := Parse("hi", nil);
if err != nil {
t.Error("unexpected parse error:", err)
}
err = tmpl.Execute("hi", nil);
if err == nil { if err == nil {
t.Error("failed to detect string as driver type") t.Error("failed to detect string as driver type")
} }
var s S;
} }
...@@ -174,7 +174,7 @@ func parse(path string, mode uint) (*ast.Program, errorList) { ...@@ -174,7 +174,7 @@ func parse(path string, mode uint) (*ast.Program, errorList) {
src, err := os.Open(path, os.O_RDONLY, 0); src, err := os.Open(path, os.O_RDONLY, 0);
defer src.Close(); defer src.Close();
if err != nil { if err != nil {
log.Stdoutf("open %s: %v", path, err); log.Stderrf("open %s: %v", path, err);
var noPos token.Position; var noPos token.Position;
return nil, errorList{parseError{noPos, err.String()}}; return nil, errorList{parseError{noPos, err.String()}};
} }
...@@ -242,7 +242,12 @@ func servePage(c *http.Conn, title, content interface{}) { ...@@ -242,7 +242,12 @@ func servePage(c *http.Conn, title, content interface{}) {
d.header = title.(string); d.header = title.(string);
d.timestamp = time.UTC().String(); d.timestamp = time.UTC().String();
d.content = content.(string); d.content = content.(string);
template.Execute(godoc_html, &d, nil, c); templ, err, line := template.Parse(godoc_html, nil);
if err != nil {
log.Stderrf("template error %s:%d: %s\n", title, line, err);
} else {
templ.Execute(&d, c);
}
} }
...@@ -350,7 +355,7 @@ func serveParseErrors(c *http.Conn, filename string, errors errorList) { ...@@ -350,7 +355,7 @@ func serveParseErrors(c *http.Conn, filename string, errors errorList) {
fmt.Fprintf(&b, "<b><font color=red>%s >>></font></b>", e.msg); fmt.Fprintf(&b, "<b><font color=red>%s >>></font></b>", e.msg);
offs = e.pos.Offset; offs = e.pos.Offset;
} else { } else {
log.Stdoutf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src)); log.Stderrf("error position %d out of bounds (len = %d)", e.pos.Offset, len(src));
} }
} }
// TODO handle Write errors // TODO handle Write errors
...@@ -471,13 +476,13 @@ func addDirectory(pmap map[string]*pakDesc, dirname string) { ...@@ -471,13 +476,13 @@ func addDirectory(pmap map[string]*pakDesc, dirname string) {
path := dirname; path := dirname;
fd, err1 := os.Open(path, os.O_RDONLY, 0); fd, err1 := os.Open(path, os.O_RDONLY, 0);
if err1 != nil { if err1 != nil {
log.Stdoutf("open %s: %v", path, err1); log.Stderrf("open %s: %v", path, err1);
return; return;
} }
list, err2 := fd.Readdir(-1); list, err2 := fd.Readdir(-1);
if err2 != nil { if err2 != nil {
log.Stdoutf("readdir %s: %v", path, err2); log.Stderrf("readdir %s: %v", path, err2);
return; return;
} }
...@@ -626,7 +631,7 @@ func installHandler(prefix string, handler func(c *http.Conn, path string)) { ...@@ -626,7 +631,7 @@ func installHandler(prefix string, handler func(c *http.Conn, path string)) {
f := func(c *http.Conn, req *http.Request) { f := func(c *http.Conn, req *http.Request) {
path := req.Url.Path; path := req.Url.Path;
if *verbose { if *verbose {
log.Stdoutf("%s\t%s", req.Host, path); log.Stderrf("%s\t%s", req.Host, path);
} }
handler(c, path[len(prefix) : len(path)]); handler(c, path[len(prefix) : len(path)]);
}; };
...@@ -663,9 +668,9 @@ func main() { ...@@ -663,9 +668,9 @@ func main() {
if *httpaddr != "" { if *httpaddr != "" {
if *verbose { if *verbose {
log.Stdoutf("Go Documentation Server\n"); log.Stderrf("Go Documentation Server\n");
log.Stdoutf("address = %s\n", *httpaddr); log.Stderrf("address = %s\n", *httpaddr);
log.Stdoutf("goroot = %s\n", goroot); log.Stderrf("goroot = %s\n", goroot);
} }
installHandler("/mem", makeFixedFileServer("doc/go_mem.html")); installHandler("/mem", makeFixedFileServer("doc/go_mem.html"));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment